UNPKG

@alwatr/fetch

Version:

Enhanced fetch API with cache strategy, retry pattern, timeout, helper methods and enhanced types.

251 lines (247 loc) 9.55 kB
/* @alwatr/fetch v5.6.2 */ "use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/main.ts var main_exports = {}; __export(main_exports, { cacheSupported: () => cacheSupported, fetch: () => fetch, fetchJson: () => fetchJson }); module.exports = __toCommonJS(main_exports); var import_http_primer2 = require("@alwatr/http-primer"); // src/core.ts var import_delay = require("@alwatr/delay"); var import_global_this = require("@alwatr/global-this"); var import_http_primer = require("@alwatr/http-primer"); var import_logger = require("@alwatr/logger"); var import_package_tracer = require("@alwatr/package-tracer"); var import_parse_duration = require("@alwatr/parse-duration"); __dev_mode__: import_package_tracer.packageTracer.add("@alwatr/fetch", "5.6.2"); var logger_ = /* @__PURE__ */ (0, import_logger.createLogger)("@alwatr/fetch"); var globalThis_ = /* @__PURE__ */ (0, import_global_this.getGlobalThis)(); var cacheStorage_; var cacheSupported = /* @__PURE__ */ Object.hasOwn(globalThis_, "caches"); var duplicateRequestStorage_ = {}; function processOptions_(options) { options.method ?? (options.method = "GET"); options.window ?? (options.window = null); options.timeout ?? (options.timeout = 8e3); options.retry ?? (options.retry = 3); options.retryDelay ?? (options.retryDelay = 1e3); options.cacheStrategy ?? (options.cacheStrategy = "network_only"); options.removeDuplicate ?? (options.removeDuplicate = "never"); options.headers ?? (options.headers = {}); if (options.cacheStrategy !== "network_only" && cacheSupported !== true) { logger_.incident?.("fetch", "fetch_cache_strategy_unsupported", { cacheSupported }); options.cacheStrategy = "network_only"; } if (options.removeDuplicate === "auto") { options.removeDuplicate = cacheSupported ? "until_load" : "always"; } if (options.url.lastIndexOf("?") === -1 && options.queryParams != null) { const queryParams = options.queryParams; const queryArray = Object.keys(queryParams).map((key) => `${key}=${String(queryParams[key])}`); if (queryArray.length > 0) { options.url += "?" + queryArray.join("&"); } } if (options.bodyJson !== void 0) { options.body = JSON.stringify(options.bodyJson); options.headers["content-type"] = import_http_primer.MimeTypes.JSON; } if (options.bearerToken !== void 0) { options.headers.authorization = `Bearer ${options.bearerToken}`; } else if (options.alwatrAuth !== void 0) { options.headers.authorization = `Alwatr ${options.alwatrAuth.userId}:${options.alwatrAuth.userToken}`; } return options; } async function handleCacheStrategy_(options) { if (options.cacheStrategy === "network_only") { return handleRemoveDuplicate_(options); } logger_.logMethod?.("_handleCacheStrategy"); if (cacheStorage_ == null && options.cacheStorageName == null) { cacheStorage_ = await caches.open("fetch_cache"); } const cacheStorage = options.cacheStorageName != null ? await caches.open(options.cacheStorageName) : cacheStorage_; const request = new Request(options.url, options); switch (options.cacheStrategy) { case "cache_first": { const cachedResponse = await cacheStorage.match(request); if (cachedResponse != null) { return cachedResponse; } const response = await handleRemoveDuplicate_(options); if (response.ok) { cacheStorage.put(request, response.clone()); } return response; } case "cache_only": { const cachedResponse = await cacheStorage.match(request); if (cachedResponse == null) { logger_.accident("_handleCacheStrategy", "fetch_cache_not_found", { url: request.url }); throw new Error("fetch_cache_not_found"); } return cachedResponse; } case "network_first": { try { const networkResponse = await handleRemoveDuplicate_(options); if (networkResponse.ok) { cacheStorage.put(request, networkResponse.clone()); } return networkResponse; } catch (err) { const cachedResponse = await cacheStorage.match(request); if (cachedResponse != null) { return cachedResponse; } throw err; } } case "update_cache": { const networkResponse = await handleRemoveDuplicate_(options); if (networkResponse.ok) { cacheStorage.put(request, networkResponse.clone()); } return networkResponse; } case "stale_while_revalidate": { const cachedResponse = await cacheStorage.match(request); const fetchedResponsePromise = handleRemoveDuplicate_(options).then((networkResponse) => { if (networkResponse.ok) { cacheStorage.put(request, networkResponse.clone()); if (typeof options.revalidateCallback === "function") { setTimeout(options.revalidateCallback, 0, networkResponse.clone()); } } return networkResponse; }); return cachedResponse ?? fetchedResponsePromise; } default: { return handleRemoveDuplicate_(options); } } } async function handleRemoveDuplicate_(options) { if (options.removeDuplicate === "never") return handleRetryPattern_(options); logger_.logMethod?.("handleRemoveDuplicate_"); const cacheKey = options.method + " " + options.url; duplicateRequestStorage_[cacheKey] ?? (duplicateRequestStorage_[cacheKey] = handleRetryPattern_(options)); try { const response = await duplicateRequestStorage_[cacheKey]; if (duplicateRequestStorage_[cacheKey] != null) { if (response.ok !== true || options.removeDuplicate === "until_load") { delete duplicateRequestStorage_[cacheKey]; } } return response.clone(); } catch (err) { delete duplicateRequestStorage_[cacheKey]; throw err; } } async function handleRetryPattern_(options) { if (!(options.retry > 1)) return handleTimeout_(options); logger_.logMethod?.("_handleRetryPattern"); options.retry--; const externalAbortSignal = options.signal; try { const response = await handleTimeout_(options); if (response.status < import_http_primer.HttpStatusCodes.Error_Server_500_Internal_Server_Error) { return response; } throw new Error("fetch_server_error"); } catch (err) { logger_.accident("fetch", "fetch_failed_retry", err); if (globalThis_.navigator?.onLine === false) { logger_.accident("handleRetryPattern_", "offline", "Skip retry because offline"); throw err; } await import_delay.delay.by(options.retryDelay); options.signal = externalAbortSignal; return handleRetryPattern_(options); } } function handleTimeout_(options) { if (options.timeout === 0) { return globalThis_.fetch(options.url, options); } logger_.logMethod?.("handleTimeout_"); return new Promise((resolved, reject) => { const abortController = typeof AbortController === "function" ? new AbortController() : null; const externalAbortSignal = options.signal; options.signal = abortController?.signal; if (abortController !== null && externalAbortSignal != null) { externalAbortSignal.addEventListener("abort", () => abortController.abort(), { once: true }); } const timeoutId = setTimeout(() => { reject(new Error("fetch_timeout")); abortController?.abort("fetch_timeout"); }, (0, import_parse_duration.parseDuration)(options.timeout)); globalThis_.fetch(options.url, options).then((response) => resolved(response)).catch((reason) => reject(reason)).finally(() => { delete options.signal; clearTimeout(timeoutId); }); }); } // src/main.ts async function fetchJson(options) { let response; let responseText; let responseJson; try { response = await fetch(options); responseText = await response.text(); responseJson = JSON.parse(responseText); if (responseJson.ok === false) { throw new Error(`fetch_response_nok`); } return responseJson; } catch (error) { const responseError = { ...responseJson, ok: false, statusCode: response?.status ?? import_http_primer2.HttpStatusCodes.Error_Server_500_Internal_Server_Error, errorCode: responseJson?.errorCode ?? error.message, errorMessage: responseJson?.errorMessage ?? error.message // responseText, }; logger_.accident("fetchJson", "fetch_json_failed", { responseError, error, responseText }); return responseError; } } function fetch(options) { options = processOptions_(options); logger_.logMethodArgs?.("fetch", { options }); return handleCacheStrategy_(options); } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { cacheSupported, fetch, fetchJson }); //# sourceMappingURL=main.cjs.map