@alwatr/fetch 5.6.6 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,34 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [6.0.0](https://github.com/Alwatr/nanolib/compare/@alwatr/fetch@5.6.7...@alwatr/fetch@6.0.0) (2025-09-06)
7
+
8
+ ### ⚠ BREAKING CHANGES
9
+
10
+ * Removed fetchJson; refactored fetch to accept url as a separate parameter, matching the web standard API.
11
+
12
+ ### 🐛 Bug Fixes
13
+
14
+ * include request body in cache key for duplicate request handling ([a891ceb](https://github.com/Alwatr/nanolib/commit/a891ceb7300b26101f5cd982409477815dad500e))
15
+ * update query parameter encoding in fetch function for proper URL formatting ([ae30c1e](https://github.com/Alwatr/nanolib/commit/ae30c1ef13eae5070c0c2865180dfa7b89aa1eba))
16
+
17
+ ### 🔨 Code Refactoring
18
+
19
+ * enhance FetchOptions type and improve fetch function handling ([a35e8e4](https://github.com/Alwatr/nanolib/commit/a35e8e495336448531b9b4ca755520517b3e3e2c))
20
+ * enhance logging in fetch and cache strategy functions for better traceability ([db0c51b](https://github.com/Alwatr/nanolib/commit/db0c51b4e5bafbba64c511dda4686226a3fcb842))
21
+ * improve documentation for fetch options and caching strategies ([d114290](https://github.com/Alwatr/nanolib/commit/d114290755d13ac5ca06a19ffe827e39b70ff92a))
22
+ * rename FetchOptions_ to AlwatrFetchOptions_ for consistency ([978947a](https://github.com/Alwatr/nanolib/commit/978947a52196f711ffc452a84edd9f34c95341b3))
23
+ * rewrite fetch module ([d245cce](https://github.com/Alwatr/nanolib/commit/d245cce8c99b345989dd18c373f682dd89ef3319))
24
+ * update fetch calls to use consistent parameters and improve response handling ([49436e6](https://github.com/Alwatr/nanolib/commit/49436e685fe8c81c78649918f3455282106bd754))
25
+ * update FetchOptions interface to enforce required properties ([4423740](https://github.com/Alwatr/nanolib/commit/4423740b3424c3d819e6c59ade183fcd303116c8))
26
+ * update FetchOptions type to AlwatrFetchOptions_ for consistency ([6c1ff26](https://github.com/Alwatr/nanolib/commit/6c1ff264a0a3937bcd6abd58010b92d53f3d76ea))
27
+
28
+ ## [5.6.7](https://github.com/Alwatr/nanolib/compare/@alwatr/fetch@5.6.6...@alwatr/fetch@5.6.7) (2025-09-05)
29
+
30
+ ### 🔗 Dependencies update
31
+
32
+ * update jest to version 30.1.3 and @types/node to version 22.18.1 ([754212b](https://github.com/Alwatr/nanolib/commit/754212b1523cfc4cfe26c9e9f6d634aa8311e0b7))
33
+
6
34
  ## [5.6.6](https://github.com/Alwatr/nanolib/compare/@alwatr/fetch@5.6.5...@alwatr/fetch@5.6.6) (2025-09-01)
7
35
 
8
36
  ### 🔗 Dependencies update
package/README.md CHANGED
@@ -1,65 +1,205 @@
1
- # Alwatr Fetch
1
+ # @alwatr/fetch
2
2
 
3
- Enhanced fetch API with cache strategy, retry pattern, timeout, helper methods and enhanced types.
3
+ ![@alwatr/fetch](./demo/alwatr-fetch.webp)
4
+
5
+ `@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.
6
+
7
+ It's designed to be a drop-in replacement for the standard `fetch` to instantly upgrade your application's network layer.
8
+
9
+ ## Key Features
10
+
11
+ - **Retry Pattern**: Automatically retries failed requests on timeouts or server errors (5xx).
12
+ - **Request Timeout**: Aborts requests that take too long to complete.
13
+ - **Duplicate Handling**: Prevents sending identical parallel requests, returning a single response for all callers.
14
+ - **Caching Strategies**: Leverages the browser's Cache API with strategies like `stale_while_revalidate`.
15
+ - **Simplified API**: Send JSON and URL parameters with ease using `bodyJson` and `queryParams`.
16
+ - **TypeScript First**: Written entirely in TypeScript for a great developer experience.
4
17
 
5
18
  ## Installation
6
19
 
20
+ Install the package using your preferred package manager:
21
+
7
22
  ```bash
23
+ # npm
24
+ npm i @alwatr/fetch
25
+
26
+ # yarn
8
27
  yarn add @alwatr/fetch
28
+
29
+ # pnpm
30
+ pnpm add @alwatr/fetch
9
31
  ```
10
32
 
11
- ## Usage
33
+ ## Quick Start
12
34
 
13
- ```ts
14
- import {fetch} from 'https://esm.run/@alwatr/fetch';
35
+ Import the `fetch` function and use it just like you would the native `fetch`. It accepts a URL and an options object with several powerful enhancements.
15
36
 
16
- const response = await fetch({
17
- url: '/api/products',
18
- queryParameters: {limit: 10},
19
- timeout: 5_000,
20
- retry: 3,
21
- cacheStrategy: 'stale_while_revalidate',
37
+ ```typescript
38
+ import {fetch} from '@alwatr/fetch';
39
+
40
+ async function fetchProducts() {
41
+ try {
42
+ console.log('Fetching product list...');
43
+ const response = await fetch('/api/products', {
44
+ queryParams: {limit: 10, category: 'electronics'},
45
+ cacheStrategy: 'stale_while_revalidate',
46
+ timeout: '5s', // Use string duration
47
+ });
48
+
49
+ if (!response.ok) {
50
+ throw new Error(`HTTP error! status: ${response.status}`);
51
+ }
52
+
53
+ const data = await response.json();
54
+ console.log('Products:', data);
55
+ }
56
+ catch (error) {
57
+ console.error('Failed to fetch products:', error);
58
+ }
59
+ }
60
+
61
+ fetchProducts();
62
+ ```
63
+
64
+ ## API and Options
65
+
66
+ The `fetch` function takes a `url` string and an `options` object. The options object extends the standard `RequestInit` and adds several custom options for enhanced control.
67
+
68
+ | Option | Type | Default | Description |
69
+ | :--- | :--- | :--- | :--- |
70
+ | `method` | `HttpMethod` | `'GET'` | The HTTP request method. |
71
+ | `headers` | `HttpRequestHeaders` | `{}` | An object representing the request's headers. |
72
+ | `timeout` | `Duration` | `8_000` (8s) | Request timeout in milliseconds or as a duration string (e.g., `'5s'`). Set to `0` to disable. |
73
+ | `retry` | `number` | `3` | Number of retries if the request fails with a server error (5xx) or times out. |
74
+ | `retryDelay` | `Duration` | `1_000` (1s) | Delay between retry attempts in milliseconds or as a duration string. |
75
+ | `removeDuplicate` | `'never' \| 'always' \| 'until_load' \| 'auto'` | `'never'` | Strategy for handling identical parallel requests. `body` is included for uniqueness. |
76
+ | `cacheStrategy` | `'network_only' \| 'network_first' \| ...` | `'network_only'` | Caching strategy using the browser's Cache API. |
77
+ | `cacheStorageName`| `string` | `'fetch_cache'` | Custom name for the `CacheStorage` instance. |
78
+ | `revalidateCallback`| `(response: Response) => void`| `undefined` | Callback executed with the new response when using `stale_while_revalidate` strategy. |
79
+ | `bodyJson` | `Json` | `undefined` | A JavaScript object sent as the request body. Sets `Content-Type` to `application/json`. |
80
+ | `queryParams` | `Dictionary` | `undefined` | An object of query parameters appended to the URL. |
81
+ | `bearerToken` | `string` | `undefined` | A bearer token added to the `Authorization` header. |
82
+ | `alwatrAuth` | `{userId: string; userToken: string}` | `undefined` | Alwatr-specific authentication credentials. |
83
+
84
+ ... and all other standard `RequestInit` properties like `signal`, `credentials`, etc.
85
+
86
+ -----
87
+
88
+ ## Features in Detail
89
+
90
+ ### Query Parameters
91
+
92
+ The `queryParams` option simplifies adding search parameters to your request URL.
93
+
94
+ ```typescript
95
+ // This will make a GET request to:
96
+ // /api/users?page=2&sort=asc
97
+ const response = await fetch('/api/users', {
98
+ queryParams: {
99
+ page: 2,
100
+ sort: 'asc',
101
+ },
22
102
  });
103
+ ```
23
104
 
24
- if (!response.ok) throw new Error('fetch_failed');
105
+ ### JSON Body
25
106
 
26
- const productList = await response.json();
107
+ Use `bodyJson` to send a JavaScript object as a JSON payload. The `Content-Type` header is automatically set to `application/json`.
27
108
 
28
- console.log(productList);
109
+ ```typescript
110
+ // This will make a POST request to /api/orders with a JSON body
111
+ const response = await fetch('/api/orders', {
112
+ method: 'POST',
113
+ bodyJson: {
114
+ productId: 'xyz-123',
115
+ quantity: 2,
116
+ },
117
+ });
29
118
  ```
30
119
 
31
- ## Fetch Options
32
-
33
- `FetchOptions` inherited from the [fetch standard parameters](https://developer.mozilla.org/en-US/docs/Web/API/fetch#parameters) and some other...
34
-
35
- - `url`: Request URL.
36
- - `bodyJson`: Body as JS Object.
37
- - `queryParameters`: URL Query Parameters as JS Object.
38
- - `timeout`: A timeout in ms for the fetch request (default `10_000`ms).
39
- - `retry`: If fetch response not acceptable or timed out, it will retry the request (default `3`).
40
- - `retryDelay`: Delay before each retries (default `1_000`).
41
- - `removeDuplicate`: Simple memory caching for remove duplicate/parallel requests (default `never`).
42
- - `never`: Never use memory caching.
43
- - `always`: Always use memory caching and remove all duplicate requests (just by method+url).
44
- - `until_load`: Cache parallel requests until request completed (it will be removed after the promise resolved).
45
- - `auto`: If CacheStorage was supported use `until_load` strategy else use `always`.
46
- - `cacheStrategy`: Strategies for caching (default `network_only`).
47
- - `network_only`: Only network request without any cache.
48
- - `network_first`: Network first, falling back to cache.
49
- - `cache_only`: Cache only without any network request.
50
- - `cache_first`: Cache first, falling back to network.
51
- - `stale_while_revalidate`: Fastest strategy, Use cached first but always request network to update the cache.
52
- - `revalidateCallback`: Revalidate callback for `stale_while_revalidate` cache strategy.
53
- - `cacheStorageName`: Cache storage custom name (default `alwatr_fetch_cache`).
54
-
55
- [Read more about standard cache strategies](https://developer.chrome.com/docs/workbox/caching-strategies-overview/#caching-strategies)
120
+ ### Timeout
56
121
 
57
- ## Sponsors
122
+ Set a timeout for your requests. If the request takes longer than the specified duration, it will be aborted, and the promise will reject with a `fetch_timeout` error.
123
+
124
+ ```typescript
125
+ await fetch('/api/slow-endpoint', {
126
+ timeout: '2.5s', // You can use duration strings
127
+ });
128
+ ```
129
+
130
+ ### Retry Pattern
131
+
132
+ The fetch operation will automatically retry on server errors (5xx status codes) or timeouts.
133
+
134
+ ```typescript
135
+ // Retry up to 5 times, with a 2-second delay between each attempt
136
+ await fetch('/api/flaky-service', {
137
+ retry: 5,
138
+ retryDelay: '2s',
139
+ });
140
+ ```
141
+
142
+ ### Duplicate Request Handling
143
+
144
+ The `removeDuplicate` option prevents multiple identical requests from being sent simultaneously. The uniqueness of a request is determined by its method, URL, and body.
145
+
146
+ - `'never'` (default): Does nothing.
147
+ - `'until_load'`: Caches the `Promise` of a request until it resolves. Subsequent identical requests will receive a clone of the first response.
148
+ - `'always'`: Caches the response indefinitely (for the lifetime of the application).
149
+ - `'auto'`: Uses `'until_load'` if the Cache API is available, otherwise `'always'`.
150
+
151
+ ```typescript
152
+ // Both calls will result in only ONE network request.
153
+ // The second call will receive the response from the first.
154
+ const [res1, res2] = await Promise.all([
155
+ fetch('/api/data', {removeDuplicate: 'until_load'}),
156
+ fetch('/api/data', {removeDuplicate: 'until_load'}),
157
+ ]);
158
+ ```
159
+
160
+ ### Cache Strategies
58
161
 
59
- The following companies, organizations, and individuals support Nanolib ongoing maintenance and development. Become a Sponsor to get your logo on our README and website.
162
+ Leverage the browser's Cache API with `cacheStrategy`.
60
163
 
61
- ### Contributing
164
+ - `'network_only'` (default): Standard fetch behavior; no caching.
165
+ - `'cache_first'`: Serves from cache if available. Otherwise, fetches from the network and caches the result.
166
+ - `'network_first'`: Fetches from the network first. If the network fails, it falls back to the cache.
167
+ - `'stale_while_revalidate'`: The fastest strategy. It serves stale content from the cache immediately while sending a network request in the background to update the cache for the next time.
168
+
169
+ ```typescript
170
+ // Serve news from cache instantly, but update it in the background for the next visit.
171
+ const response = await fetch('/api/news', {
172
+ cacheStrategy: 'stale_while_revalidate',
173
+ revalidateCallback: (freshResponse) => {
174
+ console.log('Cache updated with fresh data!');
175
+ // You can use freshResponse to update the UI if needed
176
+ },
177
+ });
178
+ ```
179
+
180
+ ### Authentication
181
+
182
+ Easily add authentication headers with `bearerToken` or the `alwatrAuth` scheme.
183
+
184
+ ```typescript
185
+ // Using a Bearer Token
186
+ await fetch('/api/secure/data', {
187
+ bearerToken: 'your-jwt-token-here',
188
+ });
189
+
190
+ // Using Alwatr's authentication scheme
191
+ await fetch('/api/secure/data', {
192
+ alwatrAuth: {
193
+ userId: 'user-id',
194
+ userToken: 'user-auth-token',
195
+ },
196
+ });
197
+ ```
198
+
199
+ ## Sponsors
62
200
 
63
- Contributions are welcome! Please read our [contribution guidelines](https://github.com/Alwatr/.github/blob/next/CONTRIBUTING.md) before submitting a pull request.
201
+ The following companies, organizations, and individuals support Nanolib's ongoing maintenance and development. Become a Sponsor to get your logo on our README and website.
64
202
 
203
+ ## Contributing
65
204
 
205
+ Contributions are welcome\! Please read our [contribution guidelines](https://github.com/Alwatr/.github/blob/next/CONTRIBUTING.md) before submitting a pull request.
package/dist/main.cjs CHANGED
@@ -1,4 +1,4 @@
1
- /* @alwatr/fetch v5.6.6 */
1
+ /* @alwatr/fetch v6.0.0 */
2
2
  "use strict";
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -22,70 +22,73 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  var main_exports = {};
23
23
  __export(main_exports, {
24
24
  cacheSupported: () => cacheSupported,
25
- fetch: () => fetch,
26
- fetchJson: () => fetchJson
25
+ fetch: () => fetch
27
26
  });
28
27
  module.exports = __toCommonJS(main_exports);
29
- var import_http_primer2 = require("@alwatr/http-primer");
30
-
31
- // src/core.ts
32
28
  var import_delay = require("@alwatr/delay");
33
29
  var import_global_this = require("@alwatr/global-this");
34
30
  var import_http_primer = require("@alwatr/http-primer");
35
31
  var import_logger = require("@alwatr/logger");
36
32
  var import_package_tracer = require("@alwatr/package-tracer");
37
33
  var import_parse_duration = require("@alwatr/parse-duration");
38
- __dev_mode__: import_package_tracer.packageTracer.add("@alwatr/fetch", "5.6.6");
34
+ __dev_mode__: import_package_tracer.packageTracer.add("@alwatr/fetch", "6.0.0");
39
35
  var logger_ = /* @__PURE__ */ (0, import_logger.createLogger)("@alwatr/fetch");
40
36
  var globalThis_ = /* @__PURE__ */ (0, import_global_this.getGlobalThis)();
41
- var cacheStorage_;
42
37
  var cacheSupported = /* @__PURE__ */ Object.hasOwn(globalThis_, "caches");
43
38
  var duplicateRequestStorage_ = {};
44
- function processOptions_(options) {
45
- options.method ??= "GET";
46
- options.window ??= null;
47
- options.timeout ??= 8e3;
48
- options.retry ??= 3;
49
- options.retryDelay ??= 1e3;
50
- options.cacheStrategy ??= "network_only";
51
- options.removeDuplicate ??= "never";
52
- options.headers ??= {};
53
- if (options.cacheStrategy !== "network_only" && cacheSupported !== true) {
54
- logger_.incident?.("fetch", "fetch_cache_strategy_unsupported", {
55
- cacheSupported
56
- });
57
- options.cacheStrategy = "network_only";
58
- }
59
- if (options.removeDuplicate === "auto") {
60
- options.removeDuplicate = cacheSupported ? "until_load" : "always";
39
+ var defaultFetchOptions = {
40
+ method: "GET",
41
+ headers: {},
42
+ timeout: 8e3,
43
+ retry: 3,
44
+ retryDelay: 1e3,
45
+ removeDuplicate: "never",
46
+ cacheStrategy: "network_only",
47
+ cacheStorageName: "fetch_cache"
48
+ };
49
+ function fetch(url, options) {
50
+ logger_.logMethodArgs?.("fetch", { url, options });
51
+ const options_ = {
52
+ ...defaultFetchOptions,
53
+ ...options,
54
+ url
55
+ };
56
+ options_.window ??= null;
57
+ if (options_.removeDuplicate === "auto") {
58
+ options_.removeDuplicate = cacheSupported ? "until_load" : "always";
61
59
  }
62
- if (options.url.lastIndexOf("?") === -1 && options.queryParams != null) {
63
- const queryParams = options.queryParams;
64
- const queryArray = Object.keys(queryParams).map((key) => `${key}=${String(queryParams[key])}`);
60
+ if (options_.url.lastIndexOf("?") === -1 && options_.queryParams != null) {
61
+ const queryParams = options_.queryParams;
62
+ const queryArray = Object.keys(queryParams).map((key) => `${encodeURIComponent(key)}=${encodeURIComponent(String(queryParams[key]))}`);
65
63
  if (queryArray.length > 0) {
66
- options.url += "?" + queryArray.join("&");
64
+ options_.url += "?" + queryArray.join("&");
67
65
  }
68
66
  }
69
- if (options.bodyJson !== void 0) {
70
- options.body = JSON.stringify(options.bodyJson);
71
- options.headers["content-type"] = import_http_primer.MimeTypes.JSON;
67
+ if (options_.bodyJson !== void 0) {
68
+ options_.body = JSON.stringify(options_.bodyJson);
69
+ options_.headers["content-type"] = import_http_primer.MimeTypes.JSON;
72
70
  }
73
- if (options.bearerToken !== void 0) {
74
- options.headers.authorization = `Bearer ${options.bearerToken}`;
75
- } else if (options.alwatrAuth !== void 0) {
76
- options.headers.authorization = `Alwatr ${options.alwatrAuth.userId}:${options.alwatrAuth.userToken}`;
71
+ if (options_.bearerToken !== void 0) {
72
+ options_.headers.authorization = `Bearer ${options_.bearerToken}`;
73
+ } else if (options_.alwatrAuth !== void 0) {
74
+ options_.headers.authorization = `Alwatr ${options_.alwatrAuth.userId}:${options_.alwatrAuth.userToken}`;
77
75
  }
78
- return options;
76
+ logger_.logProperty?.("fetch.options", options_);
77
+ return handleCacheStrategy_(options_);
79
78
  }
80
79
  async function handleCacheStrategy_(options) {
81
80
  if (options.cacheStrategy === "network_only") {
82
81
  return handleRemoveDuplicate_(options);
83
82
  }
84
- logger_.logMethod?.("_handleCacheStrategy");
85
- if (cacheStorage_ == null && options.cacheStorageName == null) {
86
- cacheStorage_ = await caches.open("fetch_cache");
83
+ logger_.logMethod?.("handleCacheStrategy_");
84
+ if (!cacheSupported) {
85
+ logger_.incident?.("fetch", "fetch_cache_strategy_unsupported", {
86
+ cacheSupported
87
+ });
88
+ options.cacheStrategy = "network_only";
89
+ return handleRemoveDuplicate_(options);
87
90
  }
88
- const cacheStorage = options.cacheStorageName != null ? await caches.open(options.cacheStorageName) : cacheStorage_;
91
+ const cacheStorage = await caches.open(options.cacheStorageName);
89
92
  const request = new Request(options.url, options);
90
93
  switch (options.cacheStrategy) {
91
94
  case "cache_first": {
@@ -148,9 +151,12 @@ async function handleCacheStrategy_(options) {
148
151
  }
149
152
  }
150
153
  async function handleRemoveDuplicate_(options) {
151
- if (options.removeDuplicate === "never") return handleRetryPattern_(options);
154
+ if (options.removeDuplicate === "never") {
155
+ return handleRetryPattern_(options);
156
+ }
152
157
  logger_.logMethod?.("handleRemoveDuplicate_");
153
- const cacheKey = options.method + " " + options.url;
158
+ const bodyString = typeof options.body === "string" ? options.body : "";
159
+ const cacheKey = `${options.method} ${options.url} ${bodyString}`;
154
160
  duplicateRequestStorage_[cacheKey] ??= handleRetryPattern_(options);
155
161
  try {
156
162
  const response = await duplicateRequestStorage_[cacheKey];
@@ -166,8 +172,10 @@ async function handleRemoveDuplicate_(options) {
166
172
  }
167
173
  }
168
174
  async function handleRetryPattern_(options) {
169
- if (!(options.retry > 1)) return handleTimeout_(options);
170
- logger_.logMethod?.("_handleRetryPattern");
175
+ if (!(options.retry > 1)) {
176
+ return handleTimeout_(options);
177
+ }
178
+ logger_.logMethod?.("handleRetryPattern_");
171
179
  options.retry--;
172
180
  const externalAbortSignal = options.signal;
173
181
  try {
@@ -204,47 +212,13 @@ function handleTimeout_(options) {
204
212
  abortController?.abort("fetch_timeout");
205
213
  }, (0, import_parse_duration.parseDuration)(options.timeout));
206
214
  globalThis_.fetch(options.url, options).then((response) => resolved(response)).catch((reason) => reject(reason)).finally(() => {
207
- delete options.signal;
208
215
  clearTimeout(timeoutId);
209
216
  });
210
217
  });
211
218
  }
212
-
213
- // src/main.ts
214
- async function fetchJson(options) {
215
- let response;
216
- let responseText;
217
- let responseJson;
218
- try {
219
- response = await fetch(options);
220
- responseText = await response.text();
221
- responseJson = JSON.parse(responseText);
222
- if (responseJson.ok === false) {
223
- throw new Error(`fetch_response_nok`);
224
- }
225
- return responseJson;
226
- } catch (error) {
227
- const responseError = {
228
- ...responseJson,
229
- ok: false,
230
- statusCode: response?.status ?? import_http_primer2.HttpStatusCodes.Error_Server_500_Internal_Server_Error,
231
- errorCode: responseJson?.errorCode ?? error.message,
232
- errorMessage: responseJson?.errorMessage ?? error.message
233
- // responseText,
234
- };
235
- logger_.accident("fetchJson", "fetch_json_failed", { responseError, error, responseText });
236
- return responseError;
237
- }
238
- }
239
- function fetch(options) {
240
- options = processOptions_(options);
241
- logger_.logMethodArgs?.("fetch", { options });
242
- return handleCacheStrategy_(options);
243
- }
244
219
  // Annotate the CommonJS export names for ESM import in node:
245
220
  0 && (module.exports = {
246
221
  cacheSupported,
247
- fetch,
248
- fetchJson
222
+ fetch
249
223
  });
250
224
  //# sourceMappingURL=main.cjs.map
package/dist/main.cjs.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/main.ts", "../src/core.ts"],
4
- "sourcesContent": ["import {HttpStatusCodes, type HttpStatusCode} from '@alwatr/http-primer';\n\nimport {handleCacheStrategy_, logger_, processOptions_, cacheSupported} from './core.js';\n\nimport type {FetchOptions, ResponseError, ResponseSuccess} from './type.js';\n\nexport {cacheSupported};\nexport type * from './type.js';\n\n/**\n * It's a wrapper around the browser's `fetch` function that adds retry pattern, timeout, cacheStrategy,\n * remove duplicates, etc.\n *\n * @see {@link FetchOptions}\n * @see {@link ResponseSuccess}\n * @see {@link ResponseError}\n *\n * @param options Fetch options.\n *\n * @returns A success or error response.\n *\n * @example\n * ```typescript\n * const responseJson = await fetchJson({\n * url: '/api/products',\n * queryParameters: {limit: 10},\n * timeout: 8_000,\n * retry: 3,\n * cacheStrategy: 'stale_while_revalidate',\n * cacheDuplicate: 'auto',\n * });\n * ```\n */\nexport async function fetchJson<T extends JsonObject>(options: FetchOptions): Promise<T | ResponseError> {\n let response;\n let responseText;\n let responseJson;\n\n try {\n response = await fetch(options);\n responseText = await response.text();\n responseJson = JSON.parse(responseText) as (ResponseError | ResponseSuccess<T>);\n if (responseJson.ok === false) {\n throw new Error(`fetch_response_nok`);\n }\n // responseJson.statusCode ??= response.status as HttpStatusCode;\n return responseJson;\n }\n catch (error) {\n const responseError: ResponseError = {\n ...responseJson,\n ok: false,\n statusCode: (response?.status as HttpStatusCode) ?? HttpStatusCodes.Error_Server_500_Internal_Server_Error,\n errorCode: (responseJson?.errorCode as string) ?? (error as Error).message,\n errorMessage: (responseJson?.errorMessage as string) ?? (error as Error).message,\n // responseText,\n };\n\n logger_.accident('fetchJson', 'fetch_json_failed', {responseError, error, responseText});\n return responseError;\n }\n}\n\n/**\n * It's a wrapper around the browser's `fetch` function that adds retry pattern, timeout, cacheStrategy,\n * remove duplicates, etc.\n *\n * @see {@link FetchOptions}\n *\n * @param options Fetch options.\n *\n * @returns A promise that resolves to the Response to that request, whether it is successful or not.\n *\n * @example\n * ```typescript\n * const response = await fetch({\n * url: '/api/products',\n * queryParameters: {limit: 10},\n * timeout: 8_000,\n * retry: 3,\n * cacheStrategy: 'stale_while_revalidate',\n * cacheDuplicate: 'auto',\n * });\n * ```\n */\nexport function fetch(options: FetchOptions): Promise<Response> {\n options = processOptions_(options);\n logger_.logMethodArgs?.('fetch', {options});\n return handleCacheStrategy_(options as Required<FetchOptions>);\n}\n", "import {delay} from '@alwatr/delay';\nimport {getGlobalThis} from '@alwatr/global-this';\nimport {HttpStatusCodes, MimeTypes} from '@alwatr/http-primer';\nimport {createLogger} from '@alwatr/logger';\nimport {packageTracer} from '@alwatr/package-tracer';\nimport {parseDuration} from '@alwatr/parse-duration';\n\nimport type {FetchOptions} from './type.js';\n\n__dev_mode__: packageTracer.add(__package_name__, __package_version__);\n\nexport const logger_ = /* #__PURE__ */ createLogger('@alwatr/fetch');\n\nconst globalThis_ = /* #__PURE__ */ getGlobalThis();\n\nlet cacheStorage_: Cache;\nexport const cacheSupported = /* #__PURE__ */ Object.hasOwn(globalThis_, 'caches');\n\nconst duplicateRequestStorage_: Record<string, Promise<Response>> = {};\n\n/**\n * Process fetch options and set defaults, etc.\n *\n * @param options Fetch options.\n *\n * @returns Required fetch options.\n */\nexport function processOptions_(options: FetchOptions): Required<FetchOptions> {\n options.method ??= 'GET';\n options.window ??= null;\n\n options.timeout ??= 8_000;\n options.retry ??= 3;\n options.retryDelay ??= 1_000;\n options.cacheStrategy ??= 'network_only';\n options.removeDuplicate ??= 'never';\n options.headers ??= {};\n\n if (options.cacheStrategy !== 'network_only' && cacheSupported !== true) {\n logger_.incident?.('fetch', 'fetch_cache_strategy_unsupported', {\n cacheSupported,\n });\n options.cacheStrategy = 'network_only';\n }\n\n if (options.removeDuplicate === 'auto') {\n options.removeDuplicate = cacheSupported ? 'until_load' : 'always';\n }\n\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) => `${key}=${String(queryParams[key])}`);\n\n if (queryArray.length > 0) {\n options.url += '?' + queryArray.join('&');\n }\n }\n\n if (options.bodyJson !== undefined) {\n options.body = JSON.stringify(options.bodyJson);\n options.headers['content-type'] = MimeTypes.JSON;\n }\n\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 return options as Required<FetchOptions>;\n}\n\n/**\n * Handle Cache Strategy over `handleRemoveDuplicate_`.\n */\nexport async function handleCacheStrategy_(options: Required<FetchOptions>): Promise<Response> {\n if (options.cacheStrategy === 'network_only') {\n return handleRemoveDuplicate_(options);\n }\n // else handle cache strategies!\n logger_.logMethod?.('_handleCacheStrategy');\n\n if (cacheStorage_ == null && options.cacheStorageName == null) {\n cacheStorage_ = await caches.open('fetch_cache');\n }\n\n const cacheStorage = options.cacheStorageName != null ? await caches.open(options.cacheStorageName) : cacheStorage_;\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 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 logger_.accident('_handleCacheStrategy', 'fetch_cache_not_found', {url: request.url});\n throw new Error('fetch_cache_not_found');\n }\n // else\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 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 * Handle Remove Duplicates over `_handleRetryPattern`.\n */\nexport async function handleRemoveDuplicate_(options: Required<FetchOptions>): Promise<Response> {\n if (options.removeDuplicate === 'never') return handleRetryPattern_(options);\n\n logger_.logMethod?.('handleRemoveDuplicate_');\n\n const cacheKey = options.method + ' ' + options.url;\n\n // We must cache fetch promise without await for handle other parallel requests.\n duplicateRequestStorage_[cacheKey] ??= handleRetryPattern_(options);\n\n try {\n // For all requests need to await for clone responses.\n const response = await duplicateRequestStorage_[cacheKey];\n\n if (duplicateRequestStorage_[cacheKey] != null) {\n if (response.ok !== true || options.removeDuplicate === 'until_load') {\n delete duplicateRequestStorage_[cacheKey];\n }\n }\n\n return response.clone();\n }\n catch (err) {\n // clean cache on any error.\n delete duplicateRequestStorage_[cacheKey];\n throw err;\n }\n}\n\n/**\n * Handle retry pattern over `handleTimeout_`.\n */\nexport async function handleRetryPattern_(options: Required<FetchOptions>): Promise<Response> {\n if (!(options.retry > 1)) return handleTimeout_(options);\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.status < HttpStatusCodes.Error_Server_500_Internal_Server_Error) {\n return response;\n }\n // else\n throw new Error('fetch_server_error');\n }\n catch (err) {\n logger_.accident('fetch', 'fetch_failed_retry', err);\n\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 options.signal = externalAbortSignal;\n return handleRetryPattern_(options);\n }\n}\n\n/**\n * It's a wrapper around the browser's `fetch` with timeout.\n */\nexport function handleTimeout_(options: FetchOptions): Promise<Response> {\n if (options.timeout === 0) {\n return globalThis_.fetch(options.url, options);\n }\n // else\n logger_.logMethod?.('handleTimeout_');\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 (abortController !== null && externalAbortSignal != null) {\n // Respect external abort signal\n externalAbortSignal.addEventListener('abort', () => abortController.abort(), {once: true});\n }\n\n const timeoutId = setTimeout(() => {\n reject(new Error('fetch_timeout'));\n abortController?.abort('fetch_timeout');\n }, parseDuration(options.timeout!));\n\n // abortController.signal.addEventListener('abort', () => {\n // logger.incident('fetch', 'fetch_abort_signal', {\n // reason: abortController.signal.reason,\n // });\n // });\n\n globalThis_\n .fetch(options.url, options)\n .then((response) => resolved(response))\n .catch((reason) => reject(reason))\n .finally(() => {\n delete options.signal; // try to avoid memory leak in nodejs!\n clearTimeout(timeoutId);\n });\n });\n}\n"],
5
- "mappings": "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",
6
- "names": ["import_http_primer"]
3
+ "sources": ["../src/main.ts"],
4
+ "sourcesContent": ["/**\n * @module @alwatr/fetch\n *\n * An enhanced, lightweight, and dependency-free wrapper for the native `fetch` API.\n * It provides modern features like caching strategies, request retries, timeouts, and\n * duplicate request handling.\n */\n\nimport {delay} from '@alwatr/delay';\nimport {getGlobalThis} from '@alwatr/global-this';\nimport {HttpStatusCodes, MimeTypes} from '@alwatr/http-primer';\nimport {createLogger} from '@alwatr/logger';\nimport {packageTracer} from '@alwatr/package-tracer';\nimport {parseDuration} from '@alwatr/parse-duration';\n\nimport type {AlwatrFetchOptions_, FetchOptions} from './type.js';\n\nexport {cacheSupported};\nexport type * from './type.js';\n\n__dev_mode__: packageTracer.add(__package_name__, __package_version__);\n\nconst logger_ = /* #__PURE__ */ createLogger('@alwatr/fetch');\nconst globalThis_ = /* #__PURE__ */ getGlobalThis();\n\n/**\n * A boolean flag indicating whether the browser's Cache API is supported.\n */\nconst cacheSupported = /* #__PURE__ */ Object.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 * 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<Response>} A promise that resolves to the `Response` object for the request.\n *\n * @example\n * ```typescript\n * async function fetchProducts() {\n * try {\n * const response = await fetch(\"/api/products\", {\n * queryParams: { limit: 10, category: \"electronics\" },\n * timeout: 5_000, // 5 seconds\n * retry: 3,\n * cacheStrategy: \"stale_while_revalidate\",\n * });\n *\n * if (!response.ok) {\n * throw new Error(`HTTP error! status: ${response.status}`);\n * }\n *\n * const data = await response.json();\n * console.log(\"Products:\", data);\n * } catch (error) {\n * console.error(\"Failed to fetch products:\", error);\n * }\n * }\n *\n * fetchProducts();\n * ```\n */\nexport function fetch(url: string, options: FetchOptions): Promise<Response> {\n logger_.logMethodArgs?.('fetch', {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 // Start the fetch lifecycle, beginning with the cache strategy.\n return handleCacheStrategy_(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 */\nasync 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 logger_.accident('_handleCacheStrategy', 'fetch_cache_not_found', {url: request.url});\n throw new Error('fetch_cache_not_found');\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 // Only retry on server errors (5xx). Client errors (4xx) are not retried.\n if (response.status < HttpStatusCodes.Error_Server_500_Internal_Server_Error) {\n return response;\n }\n // else\n\n throw new Error('fetch_server_error');\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 Error('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"],
5
+ "mappings": "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",
6
+ "names": []
7
7
  }