UNPKG

@alwatr/fetch

Version:

`@alwatr/fetch` is an enhanced, lightweight, and dependency-free wrapper for the native `fetch` API. It provides modern features like caching strategies, request retries, timeouts, and intelligent duplicate request handling, all in a compact package.

8 lines (7 loc) 25.9 kB
{ "version": 3, "sources": ["../src/core.ts", "../src/error.ts", "../src/main.ts"], "sourcesContent": ["import {delay} from '@alwatr/delay';\nimport {getGlobalThis} from '@alwatr/global-this';\nimport {hasOwn} from '@alwatr/has-own';\nimport {HttpStatusCodes, MimeTypes} from '@alwatr/http-primer';\nimport {createLogger} from '@alwatr/logger';\nimport {parseDuration} from '@alwatr/parse-duration';\n\nimport {FetchError} from './error.js';\n\nimport type {AlwatrFetchOptions_, FetchOptions} from './type.js';\n\nexport const logger_ = createLogger('@alwatr/fetch');\n\nconst globalThis_ = getGlobalThis();\n\n/**\n * A boolean flag indicating whether the browser's Cache API is supported.\n */\nexport const cacheSupported = /* #__PURE__ */ hasOwn(globalThis_, 'caches');\n\n/**\n * A simple in-memory storage for tracking and managing duplicate in-flight requests.\n * The key is a unique identifier for the request (e.g., method + URL + body),\n * and the value is the promise of the ongoing fetch operation.\n */\nconst duplicateRequestStorage_: Record<string, Promise<Response>> = {};\n\n/**\n * Default options for all fetch requests. These can be overridden by passing\n * a custom `options` object to the `fetch` function.\n */\nconst defaultFetchOptions: AlwatrFetchOptions_ = {\n method: 'GET',\n headers: {},\n timeout: 8_000,\n retry: 3,\n retryDelay: 1_000,\n removeDuplicate: 'never',\n cacheStrategy: 'network_only',\n cacheStorageName: 'fetch_cache',\n};\n\n/**\n * Internal-only fetch options type, which includes the URL and ensures all\n * optional properties from AlwatrFetchOptions_ are present.\n */\ntype FetchOptions__ = AlwatrFetchOptions_ & Omit<RequestInit, 'headers'> & {url: string};\n\n/**\n * Processes and sanitizes the fetch options.\n *\n * @param {string} url - The URL to fetch.\n * @param {FetchOptions} options - The user-provided options.\n * @returns {FetchOptions__} The processed and complete fetch options.\n * @private\n */\nexport function _processOptions(url: string, options: FetchOptions): FetchOptions__ {\n logger_.logMethodArgs?.('_processOptions', {url, options});\n\n const options_: FetchOptions__ = {\n ...defaultFetchOptions,\n ...options,\n url,\n };\n\n options_.window ??= null;\n\n if (options_.removeDuplicate === 'auto') {\n options_.removeDuplicate = cacheSupported ? 'until_load' : 'always';\n }\n\n // Append query parameters to the URL if they are provided and the URL doesn't already have them.\n if (options_.url.lastIndexOf('?') === -1 && options_.queryParams != null) {\n const queryParams = options_.queryParams;\n // prettier-ignore\n const queryArray = Object\n .keys(queryParams)\n .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(String(queryParams[key]))}`);\n\n if (queryArray.length > 0) {\n options_.url += '?' + queryArray.join('&');\n }\n }\n\n // If `bodyJson` is provided, stringify it and set the appropriate 'Content-Type' header.\n if (options_.bodyJson !== undefined) {\n options_.body = JSON.stringify(options_.bodyJson);\n options_.headers['content-type'] = MimeTypes.JSON;\n }\n\n // Set the 'Authorization' header for bearer tokens or Alwatr's authentication scheme.\n if (options_.bearerToken !== undefined) {\n options_.headers.authorization = `Bearer ${options_.bearerToken}`;\n }\n else if (options_.alwatrAuth !== undefined) {\n options_.headers.authorization = `Alwatr ${options_.alwatrAuth.userId}:${options_.alwatrAuth.userToken}`;\n }\n\n logger_.logProperty?.('fetch.options', options_);\n\n return options_;\n}\n\n/**\n * Manages caching strategies for the fetch request.\n * If the strategy is `network_only`, it bypasses caching and proceeds to the next step.\n * Otherwise, it interacts with the browser's Cache API based on the selected strategy.\n *\n * @param {FetchOptions__} options - The fully configured fetch options.\n * @returns {Promise<Response>} A promise resolving to a `Response` object, either from the cache or the network.\n * @private\n */\nexport async function handleCacheStrategy_(options: FetchOptions__): Promise<Response> {\n if (options.cacheStrategy === 'network_only') {\n return handleRemoveDuplicate_(options);\n }\n // else\n\n logger_.logMethod?.('handleCacheStrategy_');\n\n if (!cacheSupported) {\n logger_.incident?.('fetch', 'fetch_cache_strategy_unsupported', {\n cacheSupported,\n });\n // Fallback to network_only if Cache API is not available.\n options.cacheStrategy = 'network_only';\n return handleRemoveDuplicate_(options);\n }\n // else\n\n const cacheStorage = await caches.open(options.cacheStorageName);\n\n const request = new Request(options.url, options);\n\n switch (options.cacheStrategy) {\n case 'cache_first': {\n const cachedResponse = await cacheStorage.match(request);\n if (cachedResponse != null) {\n return cachedResponse;\n }\n // else\n\n const response = await handleRemoveDuplicate_(options);\n if (response.ok) {\n cacheStorage.put(request, response.clone());\n }\n return response;\n }\n\n case 'cache_only': {\n const cachedResponse = await cacheStorage.match(request);\n if (cachedResponse == null) {\n throw new FetchError('cache_not_found', 'Resource not found in cache');\n }\n // else\n\n return cachedResponse;\n }\n\n case 'network_first': {\n try {\n const networkResponse = await handleRemoveDuplicate_(options);\n if (networkResponse.ok) {\n cacheStorage.put(request, networkResponse.clone());\n }\n return networkResponse;\n }\n catch (err) {\n const cachedResponse = await cacheStorage.match(request);\n if (cachedResponse != null) {\n return cachedResponse;\n }\n // else\n\n throw err;\n }\n }\n\n case 'update_cache': {\n const networkResponse = await handleRemoveDuplicate_(options);\n if (networkResponse.ok) {\n cacheStorage.put(request, networkResponse.clone());\n }\n return networkResponse;\n }\n\n case 'stale_while_revalidate': {\n const cachedResponse = await cacheStorage.match(request);\n const fetchedResponsePromise = handleRemoveDuplicate_(options).then((networkResponse) => {\n if (networkResponse.ok) {\n cacheStorage.put(request, networkResponse.clone());\n if (typeof options.revalidateCallback === 'function') {\n setTimeout(options.revalidateCallback, 0, networkResponse.clone());\n }\n }\n return networkResponse;\n });\n\n return cachedResponse ?? fetchedResponsePromise;\n }\n\n default: {\n return handleRemoveDuplicate_(options);\n }\n }\n}\n\n/**\n * Handles duplicate request elimination.\n *\n * It creates a unique key based on the request method, URL, and body. If a request with the\n * same key is already in flight, it returns the promise of the existing request instead of\n * creating a new one. This prevents redundant network calls for identical parallel requests.\n *\n * @param {FetchOptions__} options - The fully configured fetch options.\n * @returns {Promise<Response>} A promise resolving to a cloned `Response` object.\n * @private\n */\nasync function handleRemoveDuplicate_(options: FetchOptions__): Promise<Response> {\n if (options.removeDuplicate === 'never') {\n return handleRetryPattern_(options);\n }\n // else\n\n logger_.logMethod?.('handleRemoveDuplicate_');\n\n // Create a unique key for the request. Including the body is crucial to differentiate\n // between requests to the same URL but with different payloads (e.g., POST requests).\n const bodyString = typeof options.body === 'string' ? options.body : '';\n const cacheKey = `${options.method} ${options.url} ${bodyString}`;\n\n // If a request with the same key doesn't exist, create it and store its promise.\n duplicateRequestStorage_[cacheKey] ??= handleRetryPattern_(options);\n\n try {\n // Await the shared promise to get the response.\n const response = await duplicateRequestStorage_[cacheKey];\n\n // Clean up the stored promise based on the removal strategy.\n if (duplicateRequestStorage_[cacheKey] != null) {\n if (response.ok !== true || options.removeDuplicate === 'until_load') {\n // Remove after completion for 'until_load' or if the request failed.\n delete duplicateRequestStorage_[cacheKey];\n }\n }\n\n // Return a clone of the response, so each caller can consume the body independently.\n return response.clone();\n }\n catch (err) {\n // If the request fails, remove it from storage to allow for retries.\n delete duplicateRequestStorage_[cacheKey];\n throw err;\n }\n}\n\n/**\n * Implements a retry mechanism for the fetch request.\n * If the request fails due to a server error (status >= 500) or a timeout,\n * it will be retried up to the specified number of times.\n *\n * @param {FetchOptions__} options - The fully configured fetch options.\n * @returns {Promise<Response>} A promise that resolves to the final `Response` after all retries.\n * @private\n */\nasync function handleRetryPattern_(options: FetchOptions__): Promise<Response> {\n if (!(options.retry > 1)) {\n return handleTimeout_(options);\n }\n // else\n\n logger_.logMethod?.('handleRetryPattern_');\n options.retry--;\n\n const externalAbortSignal = options.signal;\n\n try {\n const response = await handleTimeout_(options);\n\n if (!response.ok && response.status >= HttpStatusCodes.Error_Server_500_Internal_Server_Error) {\n // only retry for server errors (5xx)\n throw new FetchError('http_error', `HTTP error! status: ${response.status} ${response.statusText}`, response);\n }\n\n return response;\n }\n catch (err) {\n logger_.accident('fetch', 'fetch_failed_retry', err);\n\n // Do not retry if the browser is offline.\n if (globalThis_.navigator?.onLine === false) {\n logger_.accident('handleRetryPattern_', 'offline', 'Skip retry because offline');\n throw err;\n }\n\n await delay.by(options.retryDelay);\n\n // Restore the original signal for the next attempt.\n options.signal = externalAbortSignal;\n return handleRetryPattern_(options);\n }\n}\n\n/**\n * Wraps the native fetch call with a timeout mechanism.\n *\n * It uses an `AbortController` to abort the request if it does not complete\n * within the specified `timeout` duration. It also respects external abort signals.\n *\n * @param {FetchOptions__} options - The fully configured fetch options.\n * @returns {Promise<Response>} A promise that resolves with the `Response` or rejects on timeout.\n * @private\n */\nfunction handleTimeout_(options: FetchOptions__): Promise<Response> {\n if (options.timeout === 0) {\n // If timeout is disabled, call fetch directly.\n return globalThis_.fetch(options.url, options);\n }\n\n logger_.logMethod?.('handleTimeout_');\n\n return new Promise((resolved, reject) => {\n const abortController = typeof AbortController === 'function' ? new AbortController() : null;\n const externalAbortSignal = options.signal;\n options.signal = abortController?.signal;\n\n // If an external AbortSignal is provided, listen to it and propagate the abort.\n if (abortController !== null && externalAbortSignal != null) {\n externalAbortSignal.addEventListener('abort', () => abortController.abort(), {once: true});\n }\n\n const timeoutId = setTimeout(() => {\n reject(new FetchError('timeout', 'fetch_timeout'));\n abortController?.abort('fetch_timeout');\n }, parseDuration(options.timeout!));\n\n globalThis_\n .fetch(options.url, options)\n .then((response) => resolved(response))\n .catch((reason) => reject(reason))\n .finally(() => {\n // Clean up the timeout to prevent it from firing after the request has completed.\n clearTimeout(timeoutId);\n });\n });\n}\n", "import type { FetchErrorReason } from \"./type.js\";\n\n/**\n * Custom error class for fetch-related failures.\n *\n * This error is thrown when a fetch request fails, either due to a network issue\n * or an HTTP error status (i.e., `response.ok` is `false`). It enriches the\n * standard `Error` object with the `response` and the parsed `data` from the\n * response body, allowing for more detailed error handling.\n *\n * @example\n * ```typescript\n * const [response, error] = await fetch('/api/endpoint');\n * if (error) {\n * console.error(`Request failed with status ${error.response?.status}`);\n * console.error('Server response:', error.data);\n * }\n * ```\n */\nexport class FetchError extends Error {\n /**\n * The original `Response` object.\n * This is useful for accessing headers and other response metadata.\n * It will be `undefined` for non-HTTP errors like network failures or timeouts.\n */\n public response?: Response;\n\n /**\n * The parsed body of the error response, typically a JSON object.\n * It will be `undefined` for non-HTTP errors.\n */\n public data?: JsonObject | string;\n\n /**\n * The specific reason for the fetch failure.\n */\n public reason: FetchErrorReason;\n\n constructor(reason: FetchErrorReason, message: string, response?: Response, data?: JsonObject | string) {\n super(message);\n this.name = 'FetchError';\n this.reason = reason;\n this.response = response;\n this.data = data;\n }\n}\n", "/**\n * @module @alwatr/fetch\n *\n * An enhanced, lightweight, and dependency-free wrapper for the native `fetch`\n * API. It provides modern features like caching strategies, request retries,\n * timeouts, and duplicate request handling.\n */\n\nimport {_processOptions, handleCacheStrategy_, logger_, cacheSupported} from './core.js';\nimport {FetchError} from './error.js';\n\nimport type {FetchJsonOptions, FetchOptions, FetchResponse} from './type.js';\n\nexport {cacheSupported};\nexport * from './error.js';\nexport type * from './type.js';\n\n/**\n * An enhanced wrapper for the native `fetch` function.\n *\n * This function extends the standard `fetch` with additional features such as:\n * - **Timeout**: Aborts the request if it takes too long.\n * - **Retry Pattern**: Automatically retries the request on failure (e.g., server errors or network issues).\n * - **Duplicate Request Handling**: Prevents sending multiple identical requests in parallel.\n * - **Cache Strategies**: Provides various caching mechanisms using the browser's Cache API.\n * - **Simplified API**: Offers convenient options for adding query parameters, JSON bodies, and auth tokens.\n *\n * @see {@link FetchOptions} for a detailed list of available options.\n *\n * @param {string} url - The URL to fetch.\n * @param {FetchOptions} options - Optional configuration for the fetch request.\n * @returns {Promise<FetchResponse>} A promise that resolves to a tuple. On\n * success, it returns `[response, null]`. On failure, it returns `[null,\n * FetchError]`.\n *\n * @example\n * ```typescript\n * import {fetch} from '@alwatr/fetch';\n *\n * async function fetchProducts() {\n * const [response, error] = await fetch('/api/products', {\n * queryParams: { limit: 10 },\n * timeout: 5_000,\n * });\n *\n * if (error) {\n * console.error('Request failed:', error.reason);\n * return;\n * }\n *\n * // At this point, response is guaranteed to be valid and ok.\n * const data = await response.json();\n * console.log('Products:', data);\n * }\n *\n * fetchProducts();\n * ```\n */\nexport async function fetch(url: string, options: FetchOptions = {}): Promise<FetchResponse> {\n logger_.logMethodArgs?.('fetch', {url, options});\n\n const options_ = _processOptions(url, options);\n\n try {\n // Start the fetch lifecycle, beginning with the cache strategy.\n const response = await handleCacheStrategy_(options_);\n\n if (!response.ok) {\n throw new FetchError('http_error', `HTTP error! status: ${response.status} ${response.statusText}`, response);\n }\n\n return [response, null];\n }\n catch (err) {\n let error: FetchError;\n\n if (err instanceof FetchError) {\n error = err;\n\n if (error.response !== undefined && error.data === undefined) {\n const bodyText = await error.response.text().catch(() => '');\n\n if (bodyText.trim().length > 0) {\n try {\n // Try to parse as JSON\n error.data = JSON.parse(bodyText);\n }\n catch {\n error.data = bodyText;\n }\n }\n }\n }\n else if (err instanceof Error) {\n if (err.name === 'AbortError') {\n error = new FetchError('aborted', err.message);\n }\n else {\n error = new FetchError('network_error', err.message);\n }\n }\n else {\n error = new FetchError('unknown_error', String(err ?? 'unknown_error'));\n }\n\n logger_.error('fetch', error.reason, {error});\n return [null, error];\n }\n}\n\n/**\n * An enhanced wrapper for the native `fetch` function that automatically parses JSON responses.\n *\n * This function extends the standard `fetch` with the same features (timeout, retry, caching, etc.)\n * and automatically parses the response body as JSON. It returns a tuple with the parsed data or an error.\n *\n * @template T - The expected type of the JSON response data.\n *\n * @param {string} url - The URL to fetch.\n * @param {FetchOptions} options - Optional configuration for the fetch request.\n * @returns {Promise<[T, null] | [null, FetchError]>} A promise that resolves to a tuple.\n * On success, it returns `[data, null]` where data is the parsed JSON.\n * On failure, it returns `[null, FetchError]`.\n *\n * @example\n * ```typescript\n * import {fetchJson} from '@alwatr/fetch';\n *\n * interface Product {\n * ok: true;\n * id: number;\n * name: string;\n * price: number;\n * }\n *\n * async function getProduct(id: number) {\n * const [data, error] = await fetchJson<Product>(`/api/products/${id}`, {\n * timeout: 5_000,\n * cacheStrategy: 'cache_first',\n * requireResponseJsonWithOkTrue: true,\n * });\n *\n * if (error) {\n * console.error('Failed to fetch product:', error.reason);\n * return;\n * }\n *\n * // data is now typed as Product and guaranteed to be valid\n * console.log('Product name:', data.name);\n * }\n * ```\n */\nexport async function fetchJson<T extends JsonObject = JsonObject>(\n url: string,\n options: FetchJsonOptions = {},\n): Promise<[T, null] | [null, FetchError]> {\n logger_.logMethodArgs?.('fetchJson', {url, options});\n\n const [response, error] = await fetch(url, options);\n\n if (error) {\n return [null, error];\n }\n\n const bodyText = await response.text().catch(() => '');\n if (bodyText.trim().length === 0) {\n const parseError = new FetchError(\n 'json_parse_error',\n 'Response body is empty, cannot parse JSON',\n response,\n bodyText,\n );\n logger_.error('fetchJson', parseError.reason, {error: parseError});\n return [null, parseError];\n }\n\n try {\n const data = JSON.parse(bodyText) as T;\n if (options.requireJsonResponseWithOkTrue && data.ok !== true) {\n const parseError = new FetchError(\n 'json_response_error',\n 'Response JSON \"ok\" property is not true',\n response,\n data,\n );\n logger_.error('fetchJson', parseError.reason, {error: parseError});\n return [null, parseError];\n }\n return [data, null];\n }\n catch (err) {\n const parseError = new FetchError(\n 'json_parse_error',\n err instanceof Error ? err.message : 'Failed to parse JSON response',\n response,\n bodyText,\n );\n logger_.error('fetchJson', parseError.reason, {error: parseError});\n return [null, parseError];\n }\n}\n"], "mappings": "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", "names": [] }