UNPKG

axios-cache-interceptor

Version:
892 lines (878 loc) 34.1 kB
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); let cache_parser = require("cache-parser"); let fast_defer = require("fast-defer"); let http_vary = require("http-vary"); let object_code = require("object-code"); let try$1 = require("try"); //#region src/header/headers.ts /** * @deprecated This constant will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ const Header = { IfModifiedSince: "if-modified-since", LastModified: "last-modified", IfNoneMatch: "if-none-match", CacheControl: "cache-control", Pragma: "pragma", ETag: "etag", Expires: "expires", Age: "age", XAxiosCacheEtag: "x-axios-cache-etag", XAxiosCacheLastModified: "x-axios-cache-last-modified", XAxiosCacheStaleIfError: "x-axios-cache-stale-if-error", Vary: "vary" }; //#endregion //#region src/header/interpreter.ts /** * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ const defaultHeaderInterpreter = (headers, location) => { if (!headers) return "not enough headers"; const cacheControl = headers[Header.CacheControl]; if (cacheControl) { const cc = (0, cache_parser.parse)(String(cacheControl)); if (cc.noCache || cc.noStore || location === "server" && cc.private) return "dont cache"; if (cc.immutable) return { cache: 1e3 * 60 * 60 * 24 * 365 }; if (cc.maxAge !== void 0) { const age = headers[Header.Age]; return { cache: age ? (cc.maxAge - Number(age)) * 1e3 : cc.maxAge * 1e3, stale: cc.maxStale !== void 0 ? cc.maxStale * 1e3 : cc.staleWhileRevalidate !== void 0 ? cc.staleWhileRevalidate * 1e3 : void 0 }; } } const expires = headers[Header.Expires]; if (expires) { const milliseconds = Date.parse(String(expires)) - Date.now(); return milliseconds >= 0 ? { cache: milliseconds } : "dont cache"; } return "not enough headers"; }; //#endregion //#region src/header/extract.ts /** * Extracts specified header values from request headers. * Generic utility for extracting a subset of headers. * * @param requestHeaders The full request headers object * @param headerNames Array of header names to extract * @returns Object with extracted header values */ function extractHeaders(requestHeaders, headerNames) { const result = {}; for (const name of headerNames) result[name] = requestHeaders.get(name)?.toString(); return result; } //#endregion //#region src/util/cache-predicate.ts /** * Tests an response against a {@link CachePredicateObject}. * * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ async function testCachePredicate(response, predicate) { if (typeof predicate === "function") return predicate(response); const { statusCheck, responseMatch, containsHeaders } = predicate; if (statusCheck && !await statusCheck(response.status) || responseMatch && !await responseMatch(response)) return false; if (containsHeaders) { for (const [header, predicate] of Object.entries(containsHeaders)) if (!await predicate(response.headers[header.toLowerCase()] ?? response.headers[header])) return false; } return true; } /** * Determines whether a given URL matches a specified pattern, which can be either a * string or a regular expression. * * @param matchPattern - The pattern to match against * * - If it's a regular expression, it will be reset to ensure consistent behavior for * stateful regular expressions. * - If it's a string, the function checks if the URL contains the string. * * @param configUrl - The URL to test against the provided pattern; normally `config.url`. * @returns `true` if the `configUrl` matches the `matchPattern` * * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ function regexOrStringMatch(matchPattern, configUrl) { if (matchPattern instanceof RegExp) { matchPattern.lastIndex = 0; return matchPattern.test(configUrl); } return configUrl.includes(matchPattern); } //#endregion //#region src/interceptors/util.ts /** * Creates a new validateStatus function that will use the one already used and also * accept status code 304. * * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ function createValidateStatus(oldValidate) { return oldValidate ? (status) => oldValidate(status) || status === 304 : (status) => status >= 200 && status < 300 || status === 304; } /** * Checks if the given method is in the methods array * * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ function isMethodIn(requestMethod = "get", methodList = []) { requestMethod = requestMethod.toLowerCase(); return methodList.some((method) => method === requestMethod); } /** * This function updates the cache when the request is stale. So, the next request to the * server will be made with proper header / settings. * * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ function updateStaleRequest(cache, config) { const { etag, modifiedSince } = config.cache; const revalidation = cache.data?.meta?.revalidation; if (etag) { let etagValue; if (revalidation?.etag) etagValue = revalidation.etag; else if (etag === true) etagValue = cache.data?.headers[Header.ETag]; else etagValue = etag; if (etagValue) config.headers.set(Header.IfNoneMatch, etagValue); } if (modifiedSince) { let lastModifiedValue; if (revalidation?.lastModified) lastModifiedValue = revalidation.lastModified === true ? new Date(cache.createdAt).toUTCString() : revalidation.lastModified; else if (modifiedSince === true) lastModifiedValue = cache.data.headers[Header.LastModified] || new Date(cache.createdAt).toUTCString(); else lastModifiedValue = modifiedSince.toUTCString(); config.headers.set(Header.IfModifiedSince, lastModifiedValue); } } /** * Creates the new date to the cache by the provided response. Also handles possible 304 * Not Modified by updating response properties. * * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ function createCacheResponse(response, previousCache) { if (response.status === 304 && previousCache) { response.cached = true; response.data = previousCache.data; response.status = previousCache.status; response.statusText = previousCache.statusText; response.headers = { ...previousCache.headers, ...response.headers }; return previousCache; } return { data: response.data, status: response.status, statusText: response.statusText, headers: response.headers }; } //#endregion //#region src/interceptors/request.ts /** * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ function defaultRequestInterceptor(axios) { const onFulfilled = async (config) => { config.id = axios.generateKey(config, { vary: config.cache && Array.isArray(config.cache.vary) ? extractHeaders(config.headers, config.cache.vary) : void 0 }); if (config.cache === false) return config; config.cache = { ...axios.defaults.cache, ...config.cache }; if (config.cache.enabled === false) return config; if (typeof config.cache.cachePredicate === "object" && config.cache.cachePredicate.ignoreUrls && config.url) { for (const url of config.cache.cachePredicate.ignoreUrls) if (regexOrStringMatch(url, config.url)) return config; } if (typeof config.cache.cachePredicate === "object" && config.cache.cachePredicate.allowUrls && config.url) { let matched = false; for (const url of config.cache.cachePredicate.allowUrls) if (regexOrStringMatch(url, config.url)) { matched = true; break; } if (!matched) return config; } if (config.cache.cacheTakeover) { config.headers.set(Header.CacheControl, "no-cache, no-store, must-revalidate, max-age=0", false); config.headers.set(Header.Pragma, "no-cache", false); config.headers.set(Header.Expires, "0", false); } if (!isMethodIn(config.method, config.cache.methods)) return config; let cache = await axios.storage.get(config.id, config); const overrideCache = config.cache.override; if (config.cache.vary !== false && cache.data?.meta?.vary && cache.data.headers[Header.Vary]) { const vary = Array.isArray(config.cache.vary) ? config.cache.vary : (0, http_vary.parse)(cache.data.headers[Header.Vary]); if (vary && vary !== "*" && !(0, http_vary.compare)(vary, cache.data.meta?.vary, config.headers)) { const extractedHeaders = extractHeaders(config.headers, vary); const newKey = axios.generateKey({ ...config, id: void 0 }, { vary: extractedHeaders }); if (config.id !== newKey) { config.id = newKey; cache = await axios.storage.get(newKey, config); } } } ignoreAndRequest: if (cache.state === "empty" || cache.state === "stale" || cache.state === "must-revalidate" || overrideCache) { if (axios.waiting.has(config.id) && !overrideCache) { cache = await axios.storage.get(config.id, config); if (cache.state !== "empty" && cache.state !== "must-revalidate") break ignoreAndRequest; } const def = (0, fast_defer.deferred)(); axios.waiting.set(config.id, def); def.catch(() => void 0); await axios.storage.set(config.id, { state: "loading", previous: overrideCache ? cache.data ? "stale" : "empty" : cache.state, data: cache.data, createdAt: overrideCache && !cache.createdAt ? Date.now() : cache.createdAt }, config); if ((cache.state === "stale" || cache.state === "must-revalidate") && !overrideCache) updateStaleRequest(cache, { ...config, cache: config.cache }); config.validateStatus = createValidateStatus(config.validateStatus); if (cache.state === "stale" || cache.data && cache.state !== "must-revalidate") await config.cache.hydrate?.(cache); return config; } let cachedResponse; if (cache.state === "loading") { const deferred = axios.waiting.get(config.id); if (!deferred) { if (cache.data) await config.cache.hydrate?.(cache); return config; } try { await deferred; const state = await axios.storage.get(config.id, config); /* c8 ignore start */ if (!state.data) return onFulfilled(config); /* c8 ignore end */ if (config.cache.vary !== false && state.data.meta?.vary && state.data.headers[Header.Vary]) { const vary = Array.isArray(config.cache.vary) ? config.cache.vary : (0, http_vary.parse)(state.data.headers[Header.Vary]); if (vary && vary !== "*" && !(0, http_vary.compare)(vary, state.data.meta.vary, config.headers)) return onFulfilled(config); } cachedResponse = state.data; } catch (err) { throw err; } } else cachedResponse = cache.data; config.transformResponse = void 0; config.adapter = function cachedAdapter() { return Promise.resolve({ config, data: cachedResponse.data, headers: cachedResponse.headers, status: cachedResponse.status, statusText: cachedResponse.statusText, cached: true, stale: cache.previous === "stale", id: config.id }); }; return config; }; return { onFulfilled }; } //#endregion //#region src/util/update-cache.ts /** * Function to update all caches, from CacheProperties.update, with the new data. * * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ async function updateCache(storage, data, cacheUpdater) { if (typeof cacheUpdater === "function") return cacheUpdater(data); for (const [cacheKey, updater] of Object.entries(cacheUpdater)) { if (updater === "delete") { await storage.remove(cacheKey, data.config); continue; } const value = await storage.get(cacheKey, data.config); if (value.state === "loading") continue; const newValue = await updater(value, data); if (newValue === "delete") { await storage.remove(cacheKey, data.config); continue; } if (newValue !== "ignore") await storage.set(cacheKey, newValue, data.config); } } //#endregion //#region src/interceptors/response.ts /** * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ function defaultResponseInterceptor(axios) { /** * Replies a deferred stored in the axios waiting map. Use resolve to proceed checking the * previously updated cache or reject to abort deduplicated requests with error. */ const replyDeferred = (responseId, mode, error) => { const deferred = axios.waiting.get(responseId); if (deferred) { deferred[mode](error); axios.waiting.delete(responseId); } }; const onFulfilled = async (response) => { if (!response?.config) throw response; response.id = response.config.id; response.cached ??= false; const config = response.config; const cacheConfig = config.cache; if (response.cached) return response; if (!cacheConfig) { response.cached = false; return response; } if (cacheConfig.update) await updateCache(axios.storage, response, cacheConfig.update); if (!isMethodIn(config.method, cacheConfig.methods)) return response; const cache = await axios.storage.get(response.id, config); if (cache.state !== "loading") { axios.waiting.delete(response.id); return response; } if (!cache.data && !await testCachePredicate(response, cacheConfig.cachePredicate)) { replyDeferred(response.id, "resolve"); return response; } for (const header of Object.keys(response.headers)) if (header.startsWith("x-axios-cache")) delete response.headers[header]; let ttl = cacheConfig.ttl || -1; let staleTtl; if (cacheConfig.interpretHeader) { const expirationTime = axios.headerInterpreter(response.headers, axios.location); if (expirationTime === "dont cache") { replyDeferred(response.id, "resolve"); return response; } if (expirationTime !== "not enough headers") if (typeof expirationTime === "number") ttl = expirationTime; else { ttl = expirationTime.cache; staleTtl = expirationTime.stale; } } if (typeof ttl === "function") ttl = await ttl(response); const data = createCacheResponse(response, cache.data); if (cacheConfig.etag || cacheConfig.modifiedSince) { data.meta ??= {}; data.meta.revalidation = {}; if (cacheConfig.etag) { const etag = cacheConfig.etag === true ? response.headers[Header.ETag] : cacheConfig.etag; if (etag) data.meta.revalidation.etag = etag; } if (cacheConfig.modifiedSince) data.meta.revalidation.lastModified = cacheConfig.modifiedSince === true ? response.headers[Header.LastModified] || true : cacheConfig.modifiedSince.toUTCString(); } if (cacheConfig.vary !== false && response.headers[Header.Vary]) { const vary = Array.isArray(cacheConfig.vary) ? cacheConfig.vary : (0, http_vary.parse)(response.headers[Header.Vary]); if (Array.isArray(vary)) { data.meta ??= {}; data.meta.vary = extractHeaders(config.headers, vary); } else if (vary === "*") { await axios.storage.set(response.id, { state: "stale", createdAt: Date.now(), data, ttl }, config); replyDeferred(response.id, "resolve"); return response; } } const newCache = { state: "cached", ttl, staleTtl, createdAt: Date.now(), data }; await axios.storage.set(response.id, newCache, config); replyDeferred(response.id, "resolve"); return response; }; const onRejected = async (error) => { if (!error.isAxiosError || !error.config) throw error; const config = error.config; const id = config.id; const cacheConfig = config.cache; const response = error.response; if (!cacheConfig || !id) throw error; if (!isMethodIn(config.method, cacheConfig.methods)) { await axios.storage.remove(id, config); replyDeferred(id, "reject", error); throw error; } const cache = await axios.storage.get(id, config); if (cache.state !== "loading" || cache.previous !== "stale") { if (error.code !== "ERR_CANCELED" || error.code === "ERR_CANCELED" && cache.state !== "cached") await axios.storage.remove(id, config); if (error.code === "ERR_CANCELED") replyDeferred(id, "resolve"); else replyDeferred(id, "reject", error); throw error; } if (cacheConfig.staleIfError) { const cacheControl = String(response?.headers[Header.CacheControl]); const staleHeader = cacheControl && (0, cache_parser.parse)(cacheControl).staleIfError; const staleIfError = typeof cacheConfig.staleIfError === "function" ? await cacheConfig.staleIfError(response, cache, error) : cacheConfig.staleIfError === true && staleHeader ? staleHeader * 1e3 : cacheConfig.staleIfError; if (staleIfError === true || typeof staleIfError === "number" && cache.createdAt + staleIfError > Date.now()) { await axios.storage.set(id, { state: "stale", createdAt: Date.now(), data: cache.data }, config); const waiting = axios.waiting.get(id); if (waiting) { waiting.resolve(); axios.waiting.delete(id); } return { cached: true, stale: true, config, id, data: cache.data.data, headers: cache.data.headers, status: cache.data.status, statusText: cache.data.statusText }; } } await axios.storage.remove(id, config); replyDeferred(id, "reject", error); throw error; }; return { onFulfilled, onRejected }; } //#endregion //#region src/storage/build.ts /** * Returns true if the provided object was created from {@link buildStorage} function. * * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ const isStorage = (obj) => !!obj && !!obj["is-storage"]; /** * Migrates old header-based revalidation data to new meta.revalidation format. * This ensures backward compatibility with existing cache entries. * * @deprecated Internal migration function. Will be removed when all cache entries * have naturally expired and been recreated with new format. */ function migrateRevalidationHeaders(data) { if (data.meta?.revalidation) return; const oldEtag = data.headers[Header.XAxiosCacheEtag]; const oldLastModified = data.headers[Header.XAxiosCacheLastModified]; if (oldEtag || oldLastModified) { data.meta ??= {}; data.meta.revalidation = {}; if (oldEtag) data.meta.revalidation.etag = oldEtag; if (oldLastModified) data.meta.revalidation.lastModified = oldLastModified === "use-cache-timestamp" ? true : oldLastModified; delete data.headers[Header.XAxiosCacheEtag]; delete data.headers[Header.XAxiosCacheLastModified]; delete data.headers[Header.XAxiosCacheStaleIfError]; } } function hasRevalidationMetadata(value) { migrateRevalidationHeaders(value.data); const headers = value.data.headers; const revalidation = value.data.meta?.revalidation; return Header.ETag in headers || Header.LastModified in headers || !!(revalidation?.etag || revalidation?.lastModified); } /** Returns true if value must be revalidated */ function mustRevalidate(value) { return String(value.data.headers[Header.CacheControl]).includes("must-revalidate"); } /** Returns true if this has sufficient properties to stale instead of expire. */ function canStale(value) { if (hasRevalidationMetadata(value)) return true; return value.state === "cached" && value.staleTtl !== void 0 && Math.abs(Date.now() - (value.createdAt + value.ttl)) <= value.staleTtl; } /** * Checks if the provided cache is expired. You should also check if the cache * {@link canStale} and {@link mayUseStale} */ function isExpired(value) { return value.ttl !== void 0 && value.createdAt + value.ttl <= Date.now(); } /** * Defines which storage states are evicted first when cleaning up the storage. */ const StateEvictionOrder = { empty: 0, "must-revalidate": 1, stale: 2, cached: 3, loading: 4 }; /** * Is a comparator function that sorts storage entries by their eviction priority * and, in the same group, by older first. */ function storageEntriesSorter([, a], [, b]) { const stateDiff = StateEvictionOrder[a.state] - StateEvictionOrder[b.state]; if (stateDiff !== 0) return stateDiff; return (a.createdAt || 0) - (b.createdAt || 0); } /** * Returns true if the storage entry can be removed according to its state and the * provided maxStaleAge. */ function canRemoveStorageEntry(value, maxStaleAge) { switch (value.state) { case "loading": return false; case "empty": case "must-revalidate": return true; case "cached": return isExpired(value) && !canStale(value); case "stale": if (maxStaleAge !== void 0 && value.ttl !== void 0) return Date.now() > value.createdAt + value.ttl + maxStaleAge; return false; } } /** * All integrated storages are wrappers around the `buildStorage` function. External * libraries use it and if you want to build your own, `buildStorage` is the way to go! * * The exported `buildStorage` function abstracts the storage interface and requires a * super simple object to build the storage. * * **Note**: You can only create custom storages with this function. * * @example * * ```js * const myStorage = buildStorage({ * find: () => {...}, * set: () => {...}, * remove: () => {...}, * clear: () => {...} * }); * * const axios = setupCache(axios, { storage: myStorage }); * ``` * * @see https://axios-cache-interceptor.js.org/guide/storages#buildstorage */ function buildStorage({ set, find, remove, clear }) { return { "is-storage": 1, set, remove, clear, get: async (key, config) => { let value = await find(key, config); if (!value) return { state: "empty" }; if (value.state === "empty" || value.state === "loading" || value.state === "must-revalidate") return value; if (value.state === "cached" || value.state === "stale") migrateRevalidationHeaders(value.data); if (value.state === "cached") { if (!isExpired(value)) return value; if (!canStale(value)) { await remove(key, config); return { state: "empty" }; } value = { state: "stale", createdAt: value.createdAt, data: value.data, ttl: value.staleTtl !== void 0 ? value.staleTtl + value.ttl : void 0 }; await set(key, value, config); if (mustRevalidate(value)) return { ...value, state: "must-revalidate" }; } if (!isExpired(value)) return value; if (hasRevalidationMetadata(value)) return value; await remove(key, config); return { state: "empty" }; } }; } //#endregion //#region src/storage/memory.ts /* c8 ignore start */ /** * Clones an object using the structured clone algorithm if available, otherwise it uses * JSON.parse(JSON.stringify(value)). */ const clone = typeof structuredClone === "function" ? structuredClone : (value) => JSON.parse(JSON.stringify(value)); /* c8 ignore stop */ /** * Creates a simple in-memory storage. This means that if you need to persist data between * page or server reloads, this will not help. * * This is the storage used by default. * * If you need to modify it's data, you can do by the `data` property. * * @example * * ```js * const memoryStorage = buildMemoryStorage(); * * setupCache(axios, { storage: memoryStorage }); * * // Simple example to force delete the request cache * * const { id } = axios.get('url'); * * delete memoryStorage.data[id]; * ``` * * @param {boolean | 'double'} cloneData Use `true` if the data returned by `find()` * should be cloned to avoid mutating the original data outside the `set()` method. Use * `'double'` to also clone before saving value in storage using `set()`. Disabled is * default * @param {number | false} cleanupInterval The interval in milliseconds to run a * setInterval job of cleaning old entries. If false, the job will not be created. * 5 minutes (300_000) is default * @param {number | false} maxEntries The maximum number of entries to keep in the * storage. Its hard to determine the size of the entries, so a smart FIFO order is used * to determine eviction. If false, no check will be done and you may grow up memory * usage. 1024 is default * @param {number} maxStaleAge The maximum age in milliseconds a stale entry can stay * in the storage before being removed. Otherwise, stale-able entries would stay * indefinitely causing a memory leak eventually. 1 hour (3_600_000) is default */ function buildMemoryStorage(cloneData = false, cleanupInterval = 300 * 1e3, maxEntries = 1024, maxStaleAge = 3600 * 1e3) { function sortedEntries() { return Array.from(storage.data.entries()).sort(storageEntriesSorter); } const storage = buildStorage({ set: (key, value) => { if (maxEntries && storage.data.size >= maxEntries) { storage.cleanup(); if (storage.data.size >= maxEntries) for (const [key] of sortedEntries()) { storage.data.delete(key); if (storage.data.size < maxEntries) break; } } storage.data.set(key, cloneData === "double" ? clone(value) : value); }, remove: (key) => { storage.data.delete(key); }, find: (key) => { const value = storage.data.get(key); return cloneData && value !== void 0 ? clone(value) : value; }, clear: () => { storage.data.clear(); } }); storage.data = /* @__PURE__ */ new Map(); storage.cleanup = () => { for (const [key, value] of sortedEntries()) if (canRemoveStorageEntry(value, maxStaleAge)) storage.data.delete(key); }; if (cleanupInterval) { storage.cleaner = setInterval(storage.cleanup, cleanupInterval); if (typeof storage.cleaner === "object" && "unref" in storage.cleaner) storage.cleaner.unref(); } return storage; } //#endregion //#region src/util/key-generator.ts const SLASHES_REGEX = /^\/|\/$/g; /** * Builds an generator that receives a {@link CacheRequestConfig} and optional metadata, * and returns a value hashed by {@link hash}. * * The value is hashed into a signed integer when the returned value from the provided * generator is not a `string` or a `number`. * * You can return any type of data structure. * * @example * * ```js * // This generator will return a hash code. * // The code will only be the same if url, method and data are the same. * const generator = buildKeyGenerator(({ url, method, data }) => ({ * url, * method, * data * })); * ``` */ function buildKeyGenerator(generator) { return (request, meta) => { if (request.id) return request.id; const key = generator(request, meta); if (typeof key === "string" || typeof key === "number") return `${key}`; return `${(0, object_code.hash)(key)}`; }; } /** * @deprecated This function will be hidden in future versions. Please tell us why you need it at https://github.com/arthurfiorette/axios-cache-interceptor/issues/1158 */ const defaultKeyGenerator = buildKeyGenerator(({ baseURL, url, method, params, data }, meta) => { if (baseURL !== void 0) baseURL = baseURL.replace(SLASHES_REGEX, ""); else baseURL = ""; if (url !== void 0) url = url.replace(SLASHES_REGEX, ""); else url = ""; if (method !== void 0) method = method.toLowerCase(); else method = "get"; return { url: baseURL + (baseURL && url ? "/" : "") + url, params, method, data, ...meta }; }); //#endregion //#region src/cache/create.ts /** * Apply the caching interceptors for a already created axios instance. * * ```ts * const axios = setupCache(axios, OPTIONS); * ``` * * The `setupCache` function receives global options and all [request * specifics](https://axios-cache-interceptor.js.org/config/request-specifics) ones too. * This way, you can customize the defaults for all requests. * * @param axios The already created axios instance * @param config The config for the caching interceptors * @returns The same instance with extended typescript types. * @see https://axios-cache-interceptor.js.org/config */ function setupCache(axios, options = {}) { const axiosCache = axios; if (axiosCache.defaults.cache) throw new Error("setupCache() should be called only once"); axiosCache.location = typeof window === "undefined" ? "server" : "client"; axiosCache.storage = options.storage || buildMemoryStorage(); if (!isStorage(axiosCache.storage)) throw new Error("Use buildStorage() function"); axiosCache.waiting = options.waiting || /* @__PURE__ */ new Map(); axiosCache.generateKey = options.generateKey || defaultKeyGenerator; axiosCache.headerInterpreter = options.headerInterpreter || defaultHeaderInterpreter; axiosCache.requestInterceptor = options.requestInterceptor || defaultRequestInterceptor(axiosCache); axiosCache.responseInterceptor = options.responseInterceptor || defaultResponseInterceptor(axiosCache); axiosCache.debug = options.debug || function noop() {}; axiosCache.defaults.cache = { enabled: options.enabled ?? true, update: options.update || {}, ttl: options.ttl ?? 1e3 * 60 * 5, methods: options.methods || ["get", "head"], cachePredicate: options.cachePredicate || { statusCheck: (status) => [ 200, 203, 300, 301, 302, 404, 405, 410, 414, 501 ].includes(status) }, etag: options.etag ?? true, modifiedSince: options.modifiedSince ?? options.etag === false, interpretHeader: options.interpretHeader ?? true, cacheTakeover: options.cacheTakeover ?? true, staleIfError: options.staleIfError ?? true, override: options.override ?? false, hydrate: options.hydrate ?? void 0, vary: options.vary ?? true }; if (options.register ?? true) { axiosCache.interceptors.request.use(axiosCache.requestInterceptor.onFulfilled, axiosCache.requestInterceptor.onRejected); axiosCache.interceptors.response.use(axiosCache.responseInterceptor.onFulfilled, axiosCache.responseInterceptor.onRejected); } return axiosCache; } //#endregion //#region src/storage/web-api.ts /** * Creates a simple storage. You can persist his data by using `sessionStorage` or * `localStorage` with it. * * **ImplNote**: Without polyfill, this storage only works on browser environments. * * @example * * ```js * const fromLocalStorage = buildWebStorage(localStorage); * const fromSessionStorage = buildWebStorage(sessionStorage); * * const myStorage = new Storage(); * const fromMyStorage = buildWebStorage(myStorage); * ``` * * @param storage The type of web storage to use. localStorage or sessionStorage. * @param prefix The prefix to index the storage. Useful to prevent collision between * multiple places using the same storage. * @param {number} maxStaleAge The maximum age in milliseconds a stale entry can stay * in the storage before being removed. Otherwise, stale-able entries would stay * indefinitely causing a memory leak eventually. 1 hour (3_600_000) is default */ function buildWebStorage(storage, prefix = "axios-cache-", maxStaleAge = 3600 * 1e3) { function save(key, value) { storage.setItem(prefix + key, JSON.stringify(value)); } return buildStorage({ clear: () => { for (const key in storage) if (key.startsWith(prefix)) storage.removeItem(key); }, find: (key) => { const json = storage.getItem(prefix + key); return json ? JSON.parse(json) : void 0; }, remove: (key) => { storage.removeItem(prefix + key); }, set: (key, value) => { const result = try$1.Result.try(save, key, value); if (result.ok) return; if (!isDomQuotaExceededError(result.error)) throw result.error; const allValues = Object.entries(storage).filter(([key]) => key.startsWith(prefix)).map(([key, value]) => [key, JSON.parse(value)]); for (const [key, value] of allValues) if (canRemoveStorageEntry(value, maxStaleAge)) storage.removeItem(key); const retry = try$1.Result.try(save, key, value); if (retry.ok) return; if (!isDomQuotaExceededError(retry.error)) throw retry.error; const descItems = allValues.sort((a, b) => (a[1].createdAt || 0) - (b[1].createdAt || 0)); for (const item of descItems) { storage.removeItem(item[0]); const lastTry = try$1.Result.try(save, key, value); if (lastTry.ok) return; if (!isDomQuotaExceededError(lastTry.error)) throw lastTry.error; } } }); } function isDomQuotaExceededError(error) { return (error instanceof DOMException || typeof error === "object" && error !== null && "name" in error && error.constructor?.name === "DOMException") && "name" in error && (error.name === "QuotaExceededError" || error.name === "NS_ERROR_DOM_QUOTA_REACHED" || error.name === "QUOTA_EXCEEDED_ERR"); } //#endregion exports.Header = Header; exports.buildKeyGenerator = buildKeyGenerator; exports.buildMemoryStorage = buildMemoryStorage; exports.buildStorage = buildStorage; exports.buildWebStorage = buildWebStorage; exports.canRemoveStorageEntry = canRemoveStorageEntry; exports.canStale = canStale; exports.createCacheResponse = createCacheResponse; exports.createValidateStatus = createValidateStatus; exports.defaultHeaderInterpreter = defaultHeaderInterpreter; exports.defaultKeyGenerator = defaultKeyGenerator; exports.defaultRequestInterceptor = defaultRequestInterceptor; exports.defaultResponseInterceptor = defaultResponseInterceptor; exports.isExpired = isExpired; exports.isMethodIn = isMethodIn; exports.isStorage = isStorage; exports.mustRevalidate = mustRevalidate; exports.regexOrStringMatch = regexOrStringMatch; exports.setupCache = setupCache; exports.storageEntriesSorter = storageEntriesSorter; exports.testCachePredicate = testCachePredicate; exports.updateCache = updateCache; exports.updateStaleRequest = updateStaleRequest; //# sourceMappingURL=index.cjs.map