@alwatr/fetch
Version:
Enhanced fetch API with cache strategy, retry pattern, timeout, helper methods and enhanced types.
251 lines (247 loc) • 9.55 kB
JavaScript
/* @alwatr/fetch v5.6.2 */
;
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