@absoluteui/services/http
fetch client with interceptors, lifecycle hooks, cancellation (all, by group, latest-wins), safe retries, deduplication, ETag caching, categorized errors with default status messages, pagination, and upload progress.
import { ... } from '@absoluteui/services/http';How to use
Section titled “How to use”A fetch client for application APIs: timeouts, safe retries, cancellation, typed JSON guards, categorized errors with user-facing messages, interceptors, hooks, pagination, and uploads with progress. The HTTP client guide covers interceptors, hooks, cancellation, and error handling in depth; the recipes below are the everyday calls. The examples use createMockFetch from @absoluteui/services/http-mock; in an application, omit fetch.
How do I call a JSON API with typed results?
Section titled “How do I call a JSON API with typed results?”Create one client per API. get, post, put, patch, and delete resolve with the parsed body; pass validate to get a typed value and a bad-response error when the server sends something else.
import { createHttpClient } from '@absoluteui/services/http';import { createMockFetch } from '@absoluteui/services/http-mock';
interface Employee { id: number; name: string; department: string }const isEmployee = (d: unknown): d is Employee => typeof d === 'object' && d !== null && 'id' in d && typeof d.id === 'number' && 'name' in d && 'department' in d;
const fetch = createMockFetch([{ method: 'GET', url: '/api/employees/42', body: { id: 42, name: 'Ada Lovelace', department: 'Finance' } }]);const api = createHttpClient({ baseUrl: 'http://localhost/api', timeoutMs: 15_000, fetch });const ada = await api.get('/employees/42', { validate: isEmployee });ada.department; // => 'Finance'fetch.calls[0]?.request.url.href; // => 'http://localhost/api/employees/42'How do I show validation errors from the server next to the fields?
Section titled “How do I show validation errors from the server next to the fields?”Every failure rejects with an HttpError. Its category says what happened, message is ready for the user, and fields holds per-field messages from an RFC 9457 Problem Details body.
import { HttpError } from '@absoluteui/helpers/error';import { createHttpClient } from '@absoluteui/services/http';import { createMockFetch } from '@absoluteui/services/http-mock';
const fetch = createMockFetch([ { method: 'POST', url: '/api/leave', status: 422, body: { title: 'Invalid request', status: 422, errors: { to: 'The end date must be on or after the start date.' } } },]);const api = createHttpClient({ baseUrl: 'http://localhost/api', fetch });const error = await api.post('/leave', { from: '2026-10-09', to: '2026-10-05' }).catch((e: unknown) => e);error instanceof HttpError; // => trueconst { category, status, fields } = error as HttpError;[category, status]; // => ['validation', 422]fields; // => { to: 'The end date must be on or after the start date.' }How do I load every page of a list?
Section titled “How do I load every page of a list?”paginate is an async generator over Link headers, cursors, or offsets. It stops at a short page, at the reported total, or after maxPages (default 100).
import { createHttpClient, paginate } from '@absoluteui/services/http';import { createMockFetch } from '@absoluteui/services/http-mock';
const fetch = createMockFetch([ { url: '/api/employees?offset=0&limit=2', body: { items: [{ id: 1 }, { id: 2 }] } }, { url: '/api/employees?offset=2&limit=2', body: { items: [{ id: 3 }] } },]);const api = createHttpClient({ baseUrl: 'http://localhost/api', fetch });const ids: number[] = [];const toItems = (d: unknown): { id: number }[] => (typeof d === 'object' && d !== null && 'items' in d && Array.isArray(d.items) ? d.items : []);for await (const page of paginate(api, '/employees', { strategy: 'offset', pageSize: 2, items: toItems })) ids.push(...page.items.map((e) => e.id));ids; // => [1, 2, 3]How do I upload a file with a progress bar?
Section titled “How do I upload a file with a progress bar?”fetch cannot report upload progress, so uploadWithProgress uses XMLHttpRequest. For large files that must survive a dropped connection, use chunked uploads from @absoluteui/services/upload.
import { uploadWithProgress } from '@absoluteui/services/http';
declare const file: File; // from a file inputconst bar = document.createElement('progress');bar.max = 100;const response = await uploadWithProgress('/api/employees/42/documents', file, { onProgress: (p) => { bar.value = p.percent ?? 0; },});What goes wrong?
Section titled “What goes wrong?”- Retries apply only to idempotent requests (GET, HEAD, OPTIONS, PUT, DELETE) and to POST or PATCH with an
idempotencyKey; a plain POST is never sent twice. - A response that fails
validateis an error in thebad-responsecategory, not a wrongly typed value. - Server error text is not shown to users unless you set
trustServerMessages; the default messages never leak internals.
API reference
Section titled “API reference”Interfaces
Section titled “Interfaces”TransferProgress
Download or upload progress. total and percent are null when the size is unknown.
interface TransferProgress { readonly loaded: number; readonly total: number | null; readonly percent: number | null;}Client
Section titled “Client”Functions
Section titled “Functions”createHttpClient
Creates a fetch client with timeouts, idempotent-only retries (honouring Retry-After),
in-flight GET de-duplication, an optional ETag cache, typed JSON guards, RFC 9457 problem
details on errors, auth refresh and CSRF headers, runtime interceptors, lifecycle hooks,
cancellation (all, by group, or latest-wins), and child clients.
Every transport or HTTP failure rejects with an HttpError whose category says why:
non-2xx -> SVC_HTTP_STATUS (message from the catalog, fields from problem details);
network failure -> status 0 SVC_HTTP_NETWORK (network or offline); deadline ->
SVC_HTTP_TIMEOUT (timeout); cancellation -> SVC_HTTP_ABORTED (cancelled, never retried
or reported); unparsable or invalid body -> SVC_HTTP_INVALID_RESPONSE (bad-response);
maxResponseBytes exceeded -> SVC_HTTP_RESPONSE_TOO_LARGE (too-large). Invalid arguments
throw ValidationError. Redirects follow fetch defaults: browsers drop Authorization on
cross-origin redirects and cookies follow credentials (default 'same-origin').
function createHttpClient(options?: HttpClientOptions): HttpClient;Example:
interface User { id: number; name: string }const isUser = (d: unknown): d is User => typeof d === 'object' && d !== null && 'id' in d && 'name' in d;const api = createHttpClient({ baseUrl: '/api', auth: { getToken: async () => sessionStorage.getItem('token'), refresh: async () => { await fetch('/auth/refresh', { method: 'POST' }); } }, hooks: { onError: (error) => console.warn(error.message) },});const user = await api.get('/users/42', { validate: isUser });await api.post('/orders', { sku: 'LAPTOP-14', qty: 1 }, { idempotencyKey: true });Example:
// Interceptors transform; child clients inherit and add.import { createMockFetch } from '@absoluteui/services/http-mock';const fetch = createMockFetch([{ url: /./, respond: (r) => ({ body: { tenant: r.headers.get('x-tenant'), path: r.url.pathname } }) }]);const api = createHttpClient({ fetch, baseUrl: 'http://localhost/api' });const remove = api.interceptors.request.use((req) => { req.headers.set('x-tenant', 'acme'); return undefined; });const hr = api.extend({ baseUrl: 'http://localhost/hr' });await hr.get('/employees'); // => { tenant: 'acme', path: '/hr/employees' }remove();await api.get('/employees'); // => { tenant: null, path: '/api/employees' }Example:
// Cancellation: latest wins, groups, everything.import { createMockFetch } from '@absoluteui/services/http-mock';import { isCancelled } from '@absoluteui/services/http';const fetch = createMockFetch([{ url: /./, delayMs: 20, respond: (r) => ({ body: [r.url.searchParams.get('q')] }) }]);const api = createHttpClient({ fetch, baseUrl: 'http://localhost' });const first = api.get('/search', { query: { q: 'ad' }, cancelKey: 'people' });const second = api.get('/search', { query: { q: 'ada' }, cancelKey: 'people' });isCancelled(await first.catch((e: unknown) => e)); // => trueawait second; // => ['ada']const report = api.get('/report', { group: 'reports' });api.cancelGroup('reports'); // => 1isCancelled(await report.catch((e: unknown) => e)); // => trueExample:
// Errors and hooks: categories, default messages, one report for parallel failures.import { createMockFetch } from '@absoluteui/services/http-mock';import { HttpError } from '@absoluteui/helpers/error';const fetch = createMockFetch([{ url: /./, status: 401 }]);const reported: string[] = [];const outcomes: string[] = [];const api = createHttpClient({ fetch, retry: false, reportError: (e) => { reported.push(e.message); }, hooks: { onComplete: (r) => { outcomes.push(r.outcome); } } });const [e] = await Promise.all([api.get('http://localhost/a').catch((x: unknown) => x), api.get('http://localhost/b').catch((x: unknown) => x)]);e instanceof HttpError && e.category; // => 'unauthorized'reported; // => ['Your session has ended. Sign in again to continue.']outcomes; // => ['error', 'error']Interfaces
Section titled “Interfaces”EtagEntry
Cached representation for conditional GETs.
interface EtagEntry { readonly etag: string; readonly body: unknown;}EtagStore
Storage for the ETag cache (sync or async). Keys never contain credentials.
interface EtagStore { get(key: string): Awaitable<EtagEntry | undefined>; set(key: string, entry: EtagEntry): Awaitable<void>; delete(key: string): Awaitable<void>;}HttpAuth
Bearer-token hook. Concurrent 401s share one refresh() and each request is retried once.
interface HttpAuth { getToken(): Promise<string | null>; refresh?(): Promise<void>; readonly scheme?: string;}HttpCallInfo
One call (get, post, send, …) as hooks and pending see it.
interface HttpCallInfo { readonly id: number; readonly method: string; readonly url: string; readonly group: string | undefined; readonly cancelKey: string | undefined; readonly startedAt: number;}HttpCallResult
Passed to onComplete, exactly once per call.
interface HttpCallResult { readonly call: HttpCallInfo; readonly outcome: HttpOutcome; readonly durationMs: number; readonly response: HttpResponse<unknown> | undefined; readonly error: Error | undefined;}HttpClient
Client returned by createHttpClient.
interface HttpClient { request<T = unknown, R extends ResponseType = 'json'>(method: string, url: string, options?: RequestOptions<T, R>): Promise<ResponseTypeMap<T>[R]>; send<T = unknown, R extends ResponseType = 'json'>(method: string, url: string, options?: RequestOptions<T, R>): Promise<HttpResponse<ResponseTypeMap<T>[R]>>; get<T = unknown, R extends ResponseType = 'json'>(url: string, options?: BodylessOptions<T, R>): Promise<ResponseTypeMap<T>[R]>; head(url: string, options?: BodylessOptions<unknown, 'response'>): Promise<Response>; delete<T = unknown, R extends ResponseType = 'json'>(url: string, options?: BodylessOptions<T, R>): Promise<ResponseTypeMap<T>[R]>; post<T = unknown, R extends ResponseType = 'json'>(url: string, body?: unknown, options?: BodylessOptions<T, R>): Promise<ResponseTypeMap<T>[R]>; put<T = unknown, R extends ResponseType = 'json'>(url: string, body?: unknown, options?: BodylessOptions<T, R>): Promise<ResponseTypeMap<T>[R]>; patch<T = unknown, R extends ResponseType = 'json'>(url: string, body?: unknown, options?: BodylessOptions<T, R>): Promise<ResponseTypeMap<T>[R]>; readonly interceptors: HttpInterceptors; extend(options?: HttpClientOptions): HttpClient; readonly pending: readonly HttpCallInfo[]; cancelAll(reason?: string): number; cancelGroup(group: string, reason?: string): number; onPendingChange(listener: (count: number) => void): () => void;}HttpClientOptions
interface HttpClientOptions { readonly baseUrl?: string; readonly headers?: HeadersInit | (() => Awaitable<HeadersInit>); readonly timeoutMs?: number; readonly retry?: Partial<RetryPolicy> | false; readonly credentials?: RequestCredentials; readonly fetch?: FetchLike; readonly onRequest?: RequestInterceptor | readonly RequestInterceptor[]; readonly onResponse?: ResponseInterceptor | readonly ResponseInterceptor[]; readonly onError?: ErrorInterceptor | readonly ErrorInterceptor[]; readonly hooks?: HttpHooks; readonly dedupeGets?: boolean; readonly etagCache?: boolean | EtagStore; readonly maxResponseBytes?: number; readonly json?: SafeJsonParseOptions; readonly auth?: HttpAuth; readonly xsrf?: XsrfOptions | boolean; readonly messages?: StatusMessageCatalog; readonly trustServerMessages?: TrustServerMessages; readonly reportError?: (error: Error) => void; readonly reportDedupeMs?: number; readonly mapError?: (error: Error) => Error; readonly logger?: HttpLogger;}HttpHooks
Lifecycle observers. Hooks never change the request or response (use interceptors for that),
are not awaited, and a hook that throws or rejects is reported to logger.warn and ignored.
Client hooks run before child-client hooks, which run before per-request hooks.
interface HttpHooks { beforeRequest?(request: HttpRequestInfo): void | Promise<void>; afterRequest?(info: { readonly request: HttpRequestInfo; readonly response: Response | undefined; readonly error: Error | undefined; readonly durationMs: number; }): void | Promise<void>; beforeResponse?(info: { readonly request: HttpRequestInfo | undefined; readonly response: Response; }): void | Promise<void>; afterResponse?(info: { readonly call: HttpCallInfo; readonly response: HttpResponse<unknown>; }): void | Promise<void>; onRetry?(info: { readonly call: HttpCallInfo; readonly error: Error; readonly attempt: number; readonly delayMs: number; }): void | Promise<void>; onSuccess?(response: HttpResponse<unknown>, call: HttpCallInfo): void | Promise<void>; onError?(error: Error, call: HttpCallInfo): void | Promise<void>; onCancel?(error: HttpError, call: HttpCallInfo): void | Promise<void>; onComplete?(result: HttpCallResult): void | Promise<void>;}HttpInterceptors
Runtime interceptor registration.
interface HttpInterceptors { readonly request: InterceptorRegistry<RequestInterceptor>; readonly response: InterceptorRegistry<ResponseInterceptor>; readonly error: InterceptorRegistry<ErrorInterceptor>;}HttpLogger
Diagnostic sink; receives redacted headers and URLs without query strings only.
interface HttpLogger { debug?(message: string, context: Readonly<Record<string, unknown>>): void; warn?(message: string, context: Readonly<Record<string, unknown>>): void;}HttpRequestContext
Mutable request passed to request interceptors.
interface HttpRequestContext { method: string; url: string; readonly headers: Headers; body: BodyInit | null; credentials: RequestCredentials; cache: RequestCache | undefined; redirect: RequestRedirect | undefined; readonly signal: AbortSignal; readonly attempt: number;}HttpRequestInfo
One network exchange of a call, as hooks see it.
interface HttpRequestInfo { readonly call: HttpCallInfo; readonly method: string; readonly url: string; readonly headers: Readonly<Record<string, string>>; readonly attempt: number;}HttpResponse
Response data with metadata.
interface HttpResponse<D> { readonly data: D; readonly status: number; readonly statusText: string; readonly headers: Headers; readonly url: string; readonly fromCache: boolean; readonly requestId: string | undefined;}InterceptorRegistry
Interceptors of one kind; use returns a function that removes the interceptor again.
interface InterceptorRegistry<F> { use(interceptor: F): () => void; clear(): void; readonly size: number;}RequestInterceptors
Per-request interceptors, run after the client’s for requests and before them for responses and errors.
interface RequestInterceptors { readonly request?: RequestInterceptor | readonly RequestInterceptor[]; readonly response?: ResponseInterceptor | readonly ResponseInterceptor[]; readonly error?: ErrorInterceptor | readonly ErrorInterceptor[];}RequestOptions
interface RequestOptions<T = unknown, R extends ResponseType = ResponseType> { readonly query?: QueryInput; readonly body?: unknown; readonly headers?: HeadersInit; readonly signal?: AbortSignal | undefined; readonly responseType?: R; readonly validate?: (data: unknown) => data is T; readonly idempotencyKey?: boolean | string; readonly expectStatus?: number | readonly number[] | ((status: number) => boolean); readonly onDownloadProgress?: (progress: TransferProgress) => void; readonly timeoutMs?: number; readonly retry?: Partial<RetryPolicy> | false; readonly cache?: RequestCache; readonly credentials?: RequestCredentials; readonly redirect?: RequestRedirect; readonly dedupe?: boolean; readonly etag?: boolean; readonly group?: string; readonly cancelKey?: string; readonly interceptors?: RequestInterceptors; readonly hooks?: HttpHooks;}ResponseTypeMap
Data type produced by each ResponseType; T is the JSON payload type.
interface ResponseTypeMap<T> { json: T; text: string; blob: Blob; arrayBuffer: ArrayBuffer; stream: ReadableStream<Uint8Array>; response: Response;}RetryPolicy
Retry policy. Only idempotent methods (or requests with an idempotency key) are retried.
interface RetryPolicy { readonly retries: number; readonly methods: readonly string[]; readonly statuses: readonly number[] | ((status: number) => boolean); readonly network: boolean; readonly respectRetryAfter: boolean; readonly baseDelayMs: number; readonly maxDelayMs: number;}XsrfOptions
Double-submit CSRF protection: copies a cookie into a header for same-origin unsafe methods.
interface XsrfOptions { readonly cookieName?: string; readonly headerName?: string;}ErrorInterceptor
Called with the final error (not for cancellations); returning a 2xx Response recovers.
type ErrorInterceptor = (error: Error, request: { readonly method: string; readonly url: string;}) => Awaitable<Response | undefined>;FetchLike
Minimal fetch signature; inject one for tests or SSR.
type FetchLike = (input: string, init: RequestInit) => Promise<Response>;HttpOutcome
How a call ended.
type HttpOutcome = 'success' | 'error' | 'cancelled';RequestInterceptor
Transforms the request: change it in place, return a replacement, or return a Response to skip the network.
type RequestInterceptor = (request: HttpRequestContext) => Awaitable<HttpRequestContext | Response | undefined>;ResponseInterceptor
Transforms the response: may return a replacement Response.
type ResponseInterceptor = (response: Response, request: HttpRequestContext) => Awaitable<Response | undefined>;ResponseType
How the response body is returned.
type ResponseType = 'json' | 'text' | 'blob' | 'arrayBuffer' | 'stream' | 'response';Errors
Section titled “Errors”Functions
Section titled “Functions”isCancelled
True when error is a cancelled request (caller signal, cancelAll, cancelGroup, or a newer
call with the same cancelKey). UIs usually ignore these.
function isCancelled(error: unknown): boolean;Example:
import { HttpError } from '@absoluteui/helpers/error';isCancelled(new HttpError('SVC_HTTP_ABORTED', 'The request was cancelled.', { status: 0, category: 'cancelled' })); // => trueisCancelled(new HttpError('SVC_HTTP_STATUS', 'Not found.', { status: 404 })); // => falseisHttpError
True when value is an HttpError, optionally with the given status.
function isHttpError(value: unknown, status?: number): value is HttpError;Example:
import { HttpError } from '@absoluteui/helpers/error';const e: unknown = new HttpError('SVC_HTTP_STATUS', 'Not found.', { status: 404 });isHttpError(e, 404); // => trueisHttpError(e, 500); // => falseisHttpError(new Error('boom')); // => falseredactHeaders
Lowercased copy of headers with credential values replaced by '[REDACTED]', for logging.
function redactHeaders(headers: HeadersLike | undefined, extra?: readonly string[]): Record<string, string>;Example:
redactHeaders({ Authorization: 'Bearer abc', Accept: 'application/json' });// => { authorization: '[REDACTED]', accept: 'application/json' }requestIdOf
Correlation id of a response: X-Request-Id, X-Correlation-Id, or the trace id of
traceparent. Values that are not 1 to 128 visible ASCII characters are ignored.
function requestIdOf(headers: Headers): string | undefined;Example:
requestIdOf(new Headers({ 'x-request-id': 'req-42' })); // => 'req-42'requestIdOf(new Headers({ traceparent: '00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01' })); // => '4bf92f3577b34da6a3ce929d0e0e4736'requestIdOf(new Headers()); // => undefinedtoProblem
RFC 9457 problem details for an HttpError: the server’s document when it sent one, otherwise
a synthesized about:blank problem. undefined for other errors.
function toProblem(error: unknown): ProblemDetails | undefined;Example:
import { HttpError } from '@absoluteui/helpers/error';const error = new HttpError('SVC_HTTP_STATUS', 'Check the highlighted fields.', { status: 422, fields: { email: 'Enter a valid email address.' } });const p = toProblem(error);p?.status; // => 422p?.fieldErrors; // => { email: 'Enter a valid email address.' }toProblem(new Error('boom')); // => undefinedConstants
Section titled “Constants”SENSITIVE_HEADERS
Header names whose values redactHeaders always hides.
const SENSITIVE_HEADERS: readonly string[];HeadersLike
Headers input accepted by redactHeaders.
type HeadersLike = Headers | readonly (readonly [string, string])[] | Readonly<Record<string, string | undefined>>;TrustServerMessages
Decides whether a server-supplied message (Problem Details detail, or a top-level message
string) replaces the catalog message. Default: never (server text may leak internals or be
unlocalized).
type TrustServerMessages = boolean | ((info: { readonly status: number; readonly url: string; readonly problem: ProblemDetails | undefined;}) => boolean);Paginate
Section titled “Paginate”Functions
Section titled “Functions”paginate
Iterates pages of a JSON collection. Stops at maxPages, at the last page, or when a
next link or cursor repeats. Cross-origin next links throw SVC_HTTP_CROSS_ORIGIN_PAGE
unless allowCrossOrigin.
function paginate<Item>(client: HttpClient, url: string, options: TypedPaginateOptions<Item>): AsyncGenerator<Page<Item>, void, undefined>;function paginate(client: HttpClient, url: string, options: PaginateOptions & { readonly items?: undefined;}): AsyncGenerator<Page<unknown>, void, undefined>;Example:
import { createMockFetch } from '@absoluteui/services/http-mock';const fetch = createMockFetch([ { url: '/users?page=2', body: [{ id: 3 }] }, { url: '/users', body: [{ id: 1 }, { id: 2 }], headers: { link: '<http://localhost/users?page=2>; rel="next"' } },]);const api = createHttpClient({ fetch, baseUrl: 'http://localhost' });const ids: number[] = [];const toIds = (d: unknown): number[] => (Array.isArray(d) ? d.map((u: { id: number }) => u.id) : []);for await (const page of paginate(api, '/users', { strategy: 'link', items: toIds })) ids.push(...page.items);ids; // => [1, 2, 3]Example:
import type { HttpClient } from '@absoluteui/services/http';declare const api: HttpClient;const nextCursor = (d: unknown) => (typeof d === 'object' && d !== null && 'next' in d && typeof d.next === 'string' ? d.next : null);for await (const page of paginate(api, '/events', { strategy: 'cursor', getCursor: nextCursor })) console.log(page.index, page.items);Interfaces
Section titled “Interfaces”CursorPagination
Send the cursor from each page as a query parameter.
interface CursorPagination extends PaginateBase { readonly strategy: 'cursor'; readonly cursorParam?: string; readonly getCursor?: (data: unknown) => string | null | undefined;}LinkPagination
Follow rel="next" links from the Link header.
interface LinkPagination extends PaginateBase { readonly strategy: 'link'; readonly allowCrossOrigin?: boolean;}OffsetPagination
Increase an offset by the page size until a short page or getTotal.
interface OffsetPagination extends PaginateBase { readonly strategy: 'offset'; readonly pageSize?: number; readonly offsetParam?: string; readonly limitParam?: string; readonly getTotal?: (data: unknown) => number | undefined;}Page
One fetched page.
interface Page<Item> { readonly items: readonly Item[]; readonly data: unknown; readonly index: number; readonly status: number; readonly headers: Headers;}PaginateOptions
type PaginateOptions = LinkPagination | CursorPagination | OffsetPagination;TypedPaginateOptions
Pagination options with a typed item extractor.
type TypedPaginateOptions<Item> = PaginateOptions & { readonly items: (data: unknown) => readonly Item[];};Functions
Section titled “Functions”uploadWithProgress
Sends body with XMLHttpRequest and reports upload progress. Resolves { status, headers, body }
for 2xx; rejects with an HttpError like createHttpClient: SVC_HTTP_STATUS with problem
details, or status 0 SVC_HTTP_NETWORK (network/offline), SVC_HTTP_TIMEOUT (timeout), or
SVC_HTTP_ABORTED (cancelled).
function uploadWithProgress<R extends XhrResponseType = 'json'>(url: string, body: XMLHttpRequestBodyInit | null, options?: UploadWithProgressOptions<R>): Promise<UploadResponse<XhrResponseTypeMap[R]>>;Example:
const formData = new FormData();formData.append('file', new File(['hello'], 'a.txt', { type: 'text/plain' }));const bar = document.createElement('progress');bar.max = 100;const controller = new AbortController();const res = await uploadWithProgress('/api/files', formData, { onProgress: (p) => { bar.value = p.percent ?? 0; }, signal: controller.signal });Interfaces
Section titled “Interfaces”UploadResponse
Result of uploadWithProgress.
interface UploadResponse<D> { readonly status: number; readonly headers: Headers; readonly body: D;}UploadWithProgressOptions
interface UploadWithProgressOptions<R extends XhrResponseType = XhrResponseType> { readonly method?: string; readonly headers?: HeadersInit; readonly signal?: AbortSignal | undefined; readonly onProgress?: (progress: TransferProgress) => void; readonly timeoutMs?: number; readonly withCredentials?: boolean; readonly responseType?: R; readonly messages?: StatusMessageCatalog; readonly trustServerMessages?: TrustServerMessages; readonly json?: SafeJsonParseOptions; readonly createXhr?: () => XMLHttpRequest;}XhrResponseTypeMap
Body type per XhrResponseType.
interface XhrResponseTypeMap { json: unknown; text: string; blob: Blob; arrayBuffer: ArrayBuffer;}XhrResponseType
Response body types supported by uploadWithProgress.
type XhrResponseType = 'json' | 'text' | 'blob' | 'arrayBuffer';