request resolves to { data, error: null, meta } or { data: null, error, meta }, and never throws:
ts
import { request } from 'jg-fetch';const { data, error, meta } = await request<IUser>('https://api.example.com/users/1');if (error) { error.kind; // what failed, such as 'TIMEOUT', 'NETWORK_ERROR', or 'HTTP_ERROR' error.status; // 404, or null when no response arrived} else { data; // IUser}meta.attempts; // 1
Read a jg-api-response envelope, validate its data, and send a fresh token on every attempt:
ts
import { request } from 'jg-fetch';import { z } from 'zod';const User = z.object({ id: z.number(), name: z.string() });const { data, error } = await request('/users/me', { baseUrl: 'https://api.example.com', parseAs: 'envelope', // resolves to the envelope's data schema: User, // data is { id: number; name: string } headers: async () => ({ authorization: `Bearer ${await getIdToken()}` }),});if (error?.kind === 'API_ERROR') { error.code; // the envelope error's code, such as 'USER_NOT_FOUND' error.message; // the envelope error's message error.data; // the envelope error's data, or null without any error.body; // the whole envelope}
When a credential can expire, such as an ID token, refresh it and repeat the attempt at once, without using up a retry:
ts
await request('/users/me', { baseUrl, headers: async () => ({ authorization: `Bearer ${await getIdToken()}` }), // returning true repeats the attempt; once per call unless maxCredentialRefreshes says more refreshCredentials: async ({ status, signal }) => { if (status !== 401) { return false; } await refreshIdToken(signal); return true; },});
Send JSON, query parameters, or any other body:
ts
await request('/orders', { baseUrl, method: 'POST', body: { items: [1, 2] }, // sent as JSON, with Content-Type: application/json query: { notify: true, tags: ['a', 'b'] }, // ?notify=true&tags=a&tags=b});await request('/avatar', { baseUrl, method: 'PUT', body: formData }); // the browser sets the multipart type
A string, Blob, ArrayBuffer or view of one, FormData, URLSearchParams, or ReadableStream is sent as it is. Any other body, such as a Map, a Date, or a number, fails with INVALID_BODY before the request is sent, because fetch would turn it into text such as [object Map].
Read other kinds of responses with parseAs: 'json' (the default), 'envelope', 'text', 'bytes', 'blob', 'response' for the unread Response, or 'none'. The data’s type follows it.
Timeouts and retries
Option
Default
What it does
timeoutSeconds
15
Limits the whole call, retries and waits included.
attemptTimeoutSeconds
none
Limits each attempt; an attempt that runs out retries.
retryDelaysSeconds
[3, 5]
The waits before each retry. [] turns retries off.
retryMethods
GET, HEAD, OPTIONS
The methods that may retry. Add POST only for requests that are safe to repeat.
retryStatusCodes
408, 429, 500, 502, 503, 504
The statuses that may retry. 400–405, 409, 410, 413, and 422 never do.
nonRetryableErrorCodes
none
API error codes that never retry.
maxRetryAfterSeconds
30
The longest Retry-After a retry waits for.
maxResponseBytes
10 MiB
The largest body the buffered modes read.
Network errors, attempt timeouts, and retryable statuses retry. A 429 retries only with Retry-After, and every retry waits the longer of its delay and Retry-After. When Retry-After asks for more than maxRetryAfterSeconds, or more than the time left, the call stops and the error carries retryAfterMs. A stream body never retries.
Pass signal to cancel: its abort reports ABORTED, or TIMEOUT when its reason is a TimeoutError, such as AbortSignal.timeout(5000)’s.
Errors
The error is a FetchException, an Exception from jg-exception:
Field
Holds
kind
What failed, one of ERRORS
code, message
The envelope error’s code and message for an API_ERROR, otherwise the kind and its message
status, headers, body
The response’s status, headers, and body, parsed as JSON when it is JSON
data
The envelope error’s data for an API_ERROR, null when it has none; otherwise undefined
retryAfterMs
The wait Retry-After asked for
method, url, attempts, durationMs
The request, without its query string or fragment
cause
The original error
Messages never include the URL or the body, and url and meta.url drop the query string and fragment, where tokens such as ?key= sit.
Reading headers across origins
A browser lets the page read only a few response headers from another origin. To read Retry-After, a cf-ray, or any custom header, the API must list them in Access-Control-Expose-Headers. Otherwise headers.get() returns null, and a 429 never retries, because its Retry-After can’t be read.
Generated from TSDoc
API reference
Every export of [email protected], 26 in all, with its signature, its errors, and an example.
Parses a Retry-After header into the milliseconds to wait. It accepts a delay in seconds, such as 120, or an HTTP date, such as Wed, 21 Oct 2015 07:28:00 GMT, whose wait is counted from nowMs. A date in the past waits 0, and a wait longer than MAX_TIMEOUT_SECONDS is clamped to it. For a header with several values, such as a WebSocket handshake's, the first one counts.
Sends an HTTP request and resolves to its data or its error, never throwing. It retries network errors, attempt timeouts, and retryable statuses for the methods that may retry, waiting retryDelaysSeconds between attempts, or longer when the server sends Retry-After, all within timeoutSeconds. Each attempt sends a fresh Request. With parseAs 'envelope', it reads a jg-api-response envelope and resolves to its data, or to API_ERROR with the API's own code. With refreshCredentials, an attempt that fails with a response, such as a 401, can refresh the credentials and repeat at once, up to maxCredentialRefreshes times, without using up a retry.
The error request returns when a call fails, never throws. kind says what failed, one of the ERRORS codes. For an API error, code, message, and data are the envelope error's; otherwise code is the kind. Its message never includes the URL or the body.
Members
codeproperty
string
dataproperty
IJsonValue | undefined
severityproperty
"expected" | "critical"
shouldLogproperty
boolean
toJSONmethod
toJSON(): IExceptionJson
Converts the exception to a plain object, which JSON.stringify calls.
toStringmethod
toString(): string
Describes the exception in one line, with its code.
how request reads a successful response: - envelope: a jg-api-response envelope, resolving to its data - json: parsed JSON - text: a string - bytes: a Uint8Array - blob: a Blob - response: the Response itself, unread - none: nothing, the body is discarded
the body. Plain objects and arrays are sent as JSON, with Content-Type: application/json unless the headers set one. A string, Blob, ArrayBuffer or view of one, FormData, URLSearchParams, or ReadableStream is sent as it is. Any other value, such as a Map, a Date, or a number, is an INVALID_BODY error, sent nowhere
parseAsproperty
IParseAs
how a successful response is read. Defaults to 'json'
schemaproperty
ISchema<T>
validates the parsed data, or the envelope's data, such as a Zod schema
signalproperty
AbortSignal
aborts the call. An abort whose reason is a TimeoutError, such as AbortSignal.timeout's, reports TIMEOUT, and any other ABORTED
timeoutSecondsproperty
number
the seconds the whole call may take, retries and waits included. Defaults to DEFAULT_TIMEOUT_SECONDS
attemptTimeoutSecondsproperty
number
the seconds each attempt may take. An attempt that runs out retries. No limit by default
retryDelaysSecondsproperty
readonly number[]
the waits before each retry, in seconds; one retry per item. Defaults to DEFAULT_RETRY_DELAYS_SECONDS, and [] turns retries off
retryMethodsproperty
readonly IHttpMethod[]
the methods that may retry. Defaults to DEFAULT_RETRY_METHODS
retryStatusCodesproperty
readonly number[]
the statuses that may retry. Defaults to DEFAULT_RETRY_STATUS_CODES, and NON_RETRYABLE_STATUS_CODES never retry
nonRetryableErrorCodesproperty
readonly string[]
the API error codes, from an envelope's error, that never retry
maxRetryAfterSecondsproperty
number
the longest Retry-After wait a retry accepts, in seconds. Defaults to DEFAULT_MAX_RETRY_AFTER_SECONDS
maxResponseBytesproperty
number
the largest body the buffered parseAs modes read, counted as it streams. Defaults to DEFAULT_MAX_RESPONSE_BYTES
called when an attempt fails with a response, such as a 401, to refresh the credentials the headers function reads. Returning true repeats the attempt at once, for any method; false leaves the failure to the retry rules. Return true only for a credential failure, because the server didn't process the request then. A function that throws ends the call with CREDENTIALS_REFRESH_FAILED. Never called for a streamed body, which can be sent once
maxCredentialRefreshesproperty
number
the most refreshes a call makes, such as 2 for two kinds of credential. Defaults to 1