@alwatr/fetch 2.2.0 → 3.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.
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/main.ts"],
4
+ "sourcesContent": ["import {globalScope} from '@alwatr/global-scope';\nimport {definePackage} from '@alwatr/logger';\nimport {waitForTimeout} from '@alwatr/wait';\n\nimport type {FetchOptions} from './type';\nimport type {} from '@alwatr/nano-build';\n\nexport type * from './type';\n\nconst logger = definePackage('@alwatr/fetch', __package_version__);\n\nlet cacheStorage_: Cache;\nexport const cacheSupported = Object.hasOwn(globalScope, 'caches');\n\nconst duplicateRequestStorage_: Record<string, Promise<Response>> = {};\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\n/**\n * Process fetch options and set defaults, etc.\n *\n * @param options Fetch options.\n *\n * @returns Required fetch options.\n */\nfunction 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.queryParameters != null) {\n const queryParameters = options.queryParameters;\n // prettier-ignore\n const queryArray = Object\n .keys(queryParameters)\n .map((key) => `${key}=${String(queryParameters[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'] = 'application/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 */\nasync 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 */\nasync 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 */\nasync 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 < 500) {\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 (globalScope.navigator?.onLine === false) {\n throw new Error('offline');\n }\n\n await waitForTimeout(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 */\nfunction handleTimeout_(options: FetchOptions): Promise<Response> {\n if (options.timeout === 0) {\n return globalScope.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 }, options.timeout);\n\n // abortController.signal.addEventListener('abort', () => {\n // logger.incident('fetch', 'fetch_abort_signal', {\n // reason: abortController.signal.reason,\n // });\n // });\n\n globalScope\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": ["globalScope", "definePackage", "waitForTimeout", "logger", "cacheStorage_", "cacheSupported", "duplicateRequestStorage_", "fetch", "options", "processOptions_", "handleCacheStrategy_", "queryParameters", "queryArray", "key", "handleRemoveDuplicate_", "cacheStorage", "request", "cachedResponse", "response", "networkResponse", "err", "fetchedResponsePromise", "handleRetryPattern_", "cacheKey", "handleTimeout_", "externalAbortSignal", "resolved", "reject", "abortController", "timeoutId", "reason"]
7
+ }
package/dist/type.d.ts ADDED
@@ -0,0 +1,121 @@
1
+ import type { Dictionary, Json } from '@alwatr/type-helper';
2
+ /**
3
+ * Represents the available HTTP methods.
4
+ */
5
+ export type Methods = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'TRACE' | 'OPTIONS' | 'PATCH';
6
+ /**
7
+ * Represents a dictionary of query parameters.
8
+ * The keys are strings and the values can be strings, numbers, or booleans.
9
+ */
10
+ export type QueryParameters = Dictionary<string | number | boolean>;
11
+ /**
12
+ * Represents the cache strategy for fetching data.
13
+ *
14
+ * - 'network_only': Fetches data from the network only.
15
+ * - 'network_first': Tries to fetch data from the network first, and falls back to the cache if the network request fails.
16
+ * - 'cache_only': Fetches data from the cache only.
17
+ * - 'cache_first': Tries to fetch data from the cache first, and falls back to the network if the cache request fails.
18
+ * - 'update_cache': Fetches data from the network and updates the cache with the new data.
19
+ * - 'stale_while_revalidate': Returns the stale data from the cache while fetching updated data from the network.
20
+ */
21
+ export type CacheStrategy = 'network_only' | 'network_first' | 'cache_only' | 'cache_first' | 'update_cache' | 'stale_while_revalidate';
22
+ /**
23
+ * Represents the caching behavior for duplicate requests.
24
+ * - 'never': The response will not be cached.
25
+ * - 'always': The response will always be cached.
26
+ * - 'until_load': The response will be cached until the page is reloaded.
27
+ * - 'auto': The caching behavior will be determined automatically.
28
+ */
29
+ export type CacheDuplicate = 'never' | 'always' | 'until_load' | 'auto';
30
+ /**
31
+ * Options for the fetch request.
32
+ */
33
+ export interface FetchOptions extends RequestInit {
34
+ /**
35
+ * Request URL.
36
+ */
37
+ url: string;
38
+ /**
39
+ * A string to set the request's method.
40
+ *
41
+ * @default 'GET'
42
+ */
43
+ method?: Methods;
44
+ /**
45
+ * A Headers object to set the request's headers.
46
+ */
47
+ headers?: Dictionary<string>;
48
+ /**
49
+ * A timeout for the fetch request.
50
+ * Set `0` to disable it.
51
+ *
52
+ * Use with caution, as it may cause memory leaks in Node.js.
53
+ *
54
+ * @default 8_000 ms
55
+ */
56
+ timeout?: number;
57
+ /**
58
+ * If the fetch response is not acceptable or timed out, it will retry the request.
59
+ *
60
+ * @default 3
61
+ */
62
+ retry?: number;
63
+ /**
64
+ * Delay before each retry.
65
+ *
66
+ * @default 1_000 ms
67
+ */
68
+ retryDelay?: number;
69
+ /**
70
+ * Simple memory caching to remove duplicate/parallel requests.
71
+ *
72
+ * - `never`: Never use memory caching.
73
+ * - `always`: Always use memory caching and remove all duplicate requests.
74
+ * - `until_load`: Cache parallel requests until the request is completed (it will be removed after the promise is resolved).
75
+ * - `auto`: If CacheStorage is supported, use `until_load` strategy; otherwise, use `always`.
76
+ *
77
+ * @default 'never'
78
+ */
79
+ removeDuplicate?: CacheDuplicate;
80
+ /**
81
+ * Strategies for caching.
82
+ *
83
+ * - `network_only`: Only network request without any cache.
84
+ * - `network_first`: Network first, falling back to cache.
85
+ * - `cache_only`: Cache only without any network request.
86
+ * - `cache_first`: Cache first, falling back to network.
87
+ * - `update_cache`: Like `network_only` but with update cache.
88
+ * - `stale_while_revalidate`: Fastest strategy, use cached first but always request network to update the cache.
89
+ *
90
+ * @default 'network_only'
91
+ */
92
+ cacheStrategy?: CacheStrategy;
93
+ /**
94
+ * Revalidate callback for `stale_while_revalidate` cache strategy.
95
+ */
96
+ revalidateCallback?: (response: Response) => void | Promise<void>;
97
+ /**
98
+ * Custom name for the cache storage.
99
+ */
100
+ cacheStorageName?: string;
101
+ /**
102
+ * Body as a JavaScript object.
103
+ */
104
+ bodyJson?: Json;
105
+ /**
106
+ * URL query parameters as a JavaScript object.
107
+ */
108
+ queryParameters?: QueryParameters;
109
+ /**
110
+ * Add token to the Authentication bearer header.
111
+ */
112
+ bearerToken?: string;
113
+ /**
114
+ * Alwatr token scheme
115
+ */
116
+ alwatrAuth?: {
117
+ userId: string;
118
+ userToken: string;
119
+ };
120
+ }
121
+ //# sourceMappingURL=type.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../src/type.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,UAAU,EAAE,IAAI,EAAC,MAAM,qBAAqB,CAAC;AAE1D;;GAEG;AACH,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,OAAO,CAAC;AAE7G;;;GAGG;AACH,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;AAEpE;;;;;;;;;GASG;AACH,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,eAAe,GAAG,YAAY,GAAG,aAAa,GAAG,cAAc,GAAG,wBAAwB,CAAC;AAExI;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,QAAQ,GAAG,YAAY,GAAG,MAAM,CAAC;AAExE;;GAEG;AACH,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC/C;;OAEG;IACH,GAAG,EAAE,MAAM,CAAC;IAEZ;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IAEjB;;OAEG;IACH,OAAO,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC;IAE7B;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IAEf;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;;;;;OASG;IACH,eAAe,CAAC,EAAE,cAAc,CAAC;IAEjC;;;;;;;;;;;OAWG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAE9B;;OAEG;IACH,kBAAkB,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAElE;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAE1B;;OAEG;IACH,QAAQ,CAAC,EAAE,IAAI,CAAC;IAEhB;;OAEG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;IAElC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;OAEG;IACH,UAAU,CAAC,EAAE;QACX,MAAM,EAAE,MAAM,CAAC;QACf,SAAS,EAAE,MAAM,CAAC;KACnB,CAAA;CACF"}
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@alwatr/fetch",
3
- "version": "2.2.0",
4
- "description": "Enhanced fetch API with cache strategy, retry pattern, timeout, helper methods and enhanced types written in tiny TypeScript, ES module.",
3
+ "version": "3.0.0",
4
+ "description": "Enhanced fetch API with cache strategy, retry pattern, timeout, helper methods and enhanced types.",
5
+ "author": "S. Ali Mihandoost <ali.mihandoost@gmail.com>",
5
6
  "keywords": [
6
7
  "fetch",
7
8
  "request",
@@ -9,37 +10,79 @@
9
10
  "retry",
10
11
  "cache",
11
12
  "timeout",
13
+ "cross-platform",
14
+ "ECMAScript",
12
15
  "typescript",
16
+ "javascript",
17
+ "node",
18
+ "nodejs",
19
+ "browser",
13
20
  "esm",
21
+ "module",
22
+ "utility",
23
+ "util",
24
+ "utils",
25
+ "nanolib",
14
26
  "alwatr"
15
27
  ],
16
- "main": "fetch.js",
17
28
  "type": "module",
18
- "types": "fetch.d.ts",
19
- "author": "S. Ali Mihandoost <ali.mihandoost@gmail.com> (https://ali.mihandoost.com)",
29
+ "main": "./dist/main.cjs",
30
+ "module": "./dist/main.mjs",
31
+ "types": "./dist/main.d.ts",
32
+ "exports": {
33
+ ".": {
34
+ "import": "./dist/main.mjs",
35
+ "require": "./dist/main.cjs",
36
+ "types": "./dist/main.d.ts"
37
+ }
38
+ },
20
39
  "license": "MIT",
21
40
  "files": [
22
- "**/*.{d.ts.map,d.ts,js.map,js,html,md}"
41
+ "**/*.{js,mjs,cjs,map,d.ts,html,md}",
42
+ "!demo/**/*"
23
43
  ],
24
44
  "publishConfig": {
25
45
  "access": "public"
26
46
  },
27
47
  "repository": {
28
48
  "type": "git",
29
- "url": "https://github.com/Alwatr/alwatr-es-sdk",
49
+ "url": "https://github.com/Alwatr/nanolib",
30
50
  "directory": "packages/fetch"
31
51
  },
32
- "homepage": "https://github.com/Alwatr/alwatr-es-sdk/tree/next/packages/fetch#readme",
52
+ "homepage": "https://github.com/Alwatr/nanolib/tree/next/packages/fetch#readme",
33
53
  "bugs": {
34
- "url": "https://github.com/Alwatr/alwatr-es-sdk/issues"
54
+ "url": "https://github.com/Alwatr/nanolib/issues"
55
+ },
56
+ "prettier": "@alwatr/prettier-config",
57
+ "scripts": {
58
+ "b": "yarn run build",
59
+ "t": "yarn run test",
60
+ "w": "yarn run watch",
61
+ "c": "yarn run clean",
62
+ "cb": "yarn run clean && yarn run build",
63
+ "d": "yarn run build:es && yarn node --enable-source-maps --trace-warnings",
64
+ "build": "yarn run build:ts & yarn run build:es",
65
+ "build:es": "nano-build --preset=module",
66
+ "build:ts": "tsc --build",
67
+ "test": "NODE_OPTIONS=\"$NODE_OPTIONS --enable-source-maps --experimental-vm-modules\" jest",
68
+ "watch": "yarn run watch:ts & yarn run watch:es",
69
+ "watch:es": "yarn run build:es --watch",
70
+ "watch:ts": "yarn run build:ts --watch --preserveWatchOutput",
71
+ "clean": "rm -rfv dist *.tsbuildinfo"
35
72
  },
36
73
  "dependencies": {
37
- "@alwatr/logger": "^2.4.0",
38
- "@alwatr/util": "^1.4.0"
74
+ "@alwatr/global-scope": "^1.1.11",
75
+ "@alwatr/logger": "^3.2.3",
76
+ "@alwatr/wait": "^1.1.6"
39
77
  },
40
78
  "devDependencies": {
41
- "@alwatr/type": "^1.2.4",
42
- "@types/node": "^20.10.5"
79
+ "@alwatr/nano-build": "^1.3.1",
80
+ "@alwatr/prettier-config": "^1.0.4",
81
+ "@alwatr/tsconfig-base": "^1.1.1",
82
+ "@alwatr/type-helper": "^1.1.0",
83
+ "@types/node": "^20.11.5",
84
+ "jest": "^29.7.0",
85
+ "typescript": "^5.3.3"
43
86
  },
44
- "gitHead": "4ed3a3f1528a1e3742990083b60fa55f35850f9e"
87
+ "gitHead": "86f36bc089c81a22d5ca27b0745db65ec2f1eca3"
45
88
  }
@@ -0,0 +1,21 @@
1
+ import {fetch} from '@alwatr/fetch'
2
+
3
+ describe('fetch with search params', () => {
4
+ it('should make a GET request to the specified URL', async () => {
5
+ const options = {
6
+ url: 'http://httpbin.org/get',
7
+ queryParameters: {
8
+ a: 2
9
+ },
10
+ cacheStrategy: 'network_only',
11
+ removeDuplicate: 'auto'
12
+ };
13
+
14
+ fetch(options);
15
+ const response = await fetch(options);
16
+ const responseJson = await response.json();
17
+
18
+ expect(response.status).toBe(200);
19
+ expect(responseJson.args.a).toBe('2');
20
+ });
21
+ });
package/fetch.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"fetch.d.ts","sourceRoot":"","sources":["src/fetch.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAC,YAAY,EAAC,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,cAAc,CAAC;AAExD,mBAAmB,WAAW,CAAC;AAW/B;;GAEG;AACH,wBAAsB,cAAc,CAAC,CAAC,SAAS,qBAAqB,GAAG,qBAAqB,EAC1F,OAAO,EAAE,YAAY,GACpB,OAAO,CAAC,CAAC,CAAC,CAwDZ;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,QAAQ,CAAC,CAI9D"}
package/fetch.js DELETED
@@ -1,293 +0,0 @@
1
- import { createLogger, definePackage, NODE_MODE, globalScope } from '@alwatr/logger';
2
- import { getClientId, delay } from '@alwatr/util';
3
- const logger = createLogger('alwatr/fetch');
4
- definePackage('fetch', '2.x');
5
- let alwatrCacheStorage;
6
- const cacheSupported = 'caches' in globalScope;
7
- const duplicateRequestStorage = {};
8
- /**
9
- * Fetch from alwatr services and return standard response.
10
- */
11
- export async function serviceRequest(options) {
12
- logger.logMethodArgs?.('serviceRequest', { url: options.url });
13
- if (!NODE_MODE) {
14
- options.headers ?? (options.headers = {});
15
- if (!options.headers['client-id']) {
16
- options.headers['client-id'] = getClientId();
17
- }
18
- }
19
- let response;
20
- try {
21
- response = await fetch(options);
22
- }
23
- catch (err) {
24
- const errMessage = err.message;
25
- if (errMessage !== 'fetch_cache_not_found') {
26
- logger.error('serviceRequest', err.message || 'fetch_failed', err, options);
27
- }
28
- throw err;
29
- }
30
- let responseText;
31
- try {
32
- responseText = await response.text();
33
- }
34
- catch (err) {
35
- logger.error('serviceRequest', 'invalid_response', err, {
36
- response,
37
- });
38
- throw err;
39
- }
40
- let responseJson;
41
- try {
42
- responseJson = JSON.parse(responseText);
43
- }
44
- catch (err) {
45
- logger.error('serviceRequest', 'invalid_json', err, { responseText });
46
- throw err;
47
- }
48
- if (responseJson.ok !== true) {
49
- if (typeof responseJson.errorCode === 'string') {
50
- logger.accident('serviceRequest', responseJson.errorCode, { responseJson });
51
- throw new Error(responseJson.errorCode);
52
- }
53
- else {
54
- logger.error('serviceRequest', 'fetch_nok', { responseJson });
55
- throw new Error('fetch_nok');
56
- }
57
- }
58
- // TODO: generate fetch signals hook (for easier handle loading and show error toast)
59
- return responseJson;
60
- }
61
- /**
62
- * It's a wrapper around the browser's `fetch` function that adds retry pattern, timeout, cacheStrategy,
63
- * remove duplicates, etc.
64
- *
65
- * Example:
66
- *
67
- * ```ts
68
- * const response = await fetch({
69
- * url: '/api/products',
70
- * queryParameters: {limit: 10},
71
- * timeout: 10_000,
72
- * retry: 3,
73
- * cacheStrategy: 'stale_while_revalidate',
74
- * cacheDuplicate: 'auto',
75
- * });
76
- * ```
77
- */
78
- export function fetch(options) {
79
- options = _processOptions(options);
80
- logger.logMethodArgs?.('fetch', { options });
81
- return _handleCacheStrategy(options);
82
- }
83
- /**
84
- * Process fetch options and set defaults, etc.
85
- */
86
- function _processOptions(options) {
87
- options.method ?? (options.method = 'GET');
88
- options.window ?? (options.window = null);
89
- options.timeout ?? (options.timeout = 10000);
90
- options.retry ?? (options.retry = 3);
91
- options.retryDelay ?? (options.retryDelay = 1000);
92
- options.cacheStrategy ?? (options.cacheStrategy = 'network_only');
93
- options.removeDuplicate ?? (options.removeDuplicate = 'never');
94
- options.headers ?? (options.headers = {});
95
- if (options.cacheStrategy !== 'network_only' && cacheSupported !== true) {
96
- logger.incident?.('fetch', 'fetch_cache_strategy_unsupported', {
97
- cacheSupported,
98
- });
99
- options.cacheStrategy = 'network_only';
100
- }
101
- if (options.removeDuplicate === 'auto') {
102
- options.removeDuplicate = cacheSupported ? 'until_load' : 'always';
103
- }
104
- if (options.url.lastIndexOf('?') === -1 && options.queryParameters != null) {
105
- const queryParameters = options.queryParameters;
106
- // prettier-ignore
107
- const queryArray = Object
108
- .keys(queryParameters)
109
- .map((key) => `${key}=${String(queryParameters[key])}`);
110
- if (queryArray.length > 0) {
111
- options.url += '?' + queryArray.join('&');
112
- }
113
- }
114
- if (options.bodyJson != null) {
115
- options.body = JSON.stringify(options.bodyJson);
116
- options.headers['Content-Type'] = 'application/json';
117
- }
118
- if (options.token != null) {
119
- options.headers.Authorization = `Bearer ${options.token}`;
120
- }
121
- if (options.userAuth != null) {
122
- options.headers.Authorization = `Bearer ${options.userAuth.id}/${options.userAuth.token}`;
123
- }
124
- return options;
125
- }
126
- /**
127
- * Handle Cache Strategy over `_handleRemoveDuplicate`.
128
- */
129
- async function _handleCacheStrategy(options) {
130
- if (options.cacheStrategy === 'network_only') {
131
- return _handleRemoveDuplicate(options);
132
- }
133
- // else handle cache strategies!
134
- logger.logMethod?.('_handleCacheStrategy');
135
- if (alwatrCacheStorage == null && options.cacheStorageName == null) {
136
- alwatrCacheStorage = await caches.open('alwatr_fetch_cache');
137
- }
138
- const cacheStorage = options.cacheStorageName != null ? await caches.open(options.cacheStorageName) : alwatrCacheStorage;
139
- const request = new Request(options.url, options);
140
- switch (options.cacheStrategy) {
141
- case 'cache_first': {
142
- const cachedResponse = await cacheStorage.match(request);
143
- if (cachedResponse != null) {
144
- return cachedResponse;
145
- }
146
- // else
147
- const response = await _handleRemoveDuplicate(options);
148
- if (response.ok) {
149
- cacheStorage.put(request, response.clone());
150
- }
151
- return response;
152
- }
153
- case 'cache_only': {
154
- const cachedResponse = await cacheStorage.match(request);
155
- if (cachedResponse == null) {
156
- logger.accident('_handleCacheStrategy', 'fetch_cache_not_found', { url: request.url });
157
- throw new Error('fetch_cache_not_found');
158
- }
159
- // else
160
- return cachedResponse;
161
- }
162
- case 'network_first': {
163
- try {
164
- const networkResponse = await _handleRemoveDuplicate(options);
165
- if (networkResponse.ok) {
166
- cacheStorage.put(request, networkResponse.clone());
167
- }
168
- return networkResponse;
169
- }
170
- catch (err) {
171
- const cachedResponse = await cacheStorage.match(request);
172
- if (cachedResponse != null) {
173
- return cachedResponse;
174
- }
175
- // else
176
- throw err;
177
- }
178
- }
179
- case 'update_cache': {
180
- const networkResponse = await _handleRemoveDuplicate(options);
181
- if (networkResponse.ok) {
182
- cacheStorage.put(request, networkResponse.clone());
183
- }
184
- return networkResponse;
185
- }
186
- case 'stale_while_revalidate': {
187
- const cachedResponse = await cacheStorage.match(request);
188
- const fetchedResponsePromise = _handleRemoveDuplicate(options).then((networkResponse) => {
189
- if (networkResponse.ok) {
190
- cacheStorage.put(request, networkResponse.clone());
191
- if (typeof options.revalidateCallback === 'function') {
192
- setTimeout(options.revalidateCallback, 0, networkResponse.clone());
193
- }
194
- }
195
- return networkResponse;
196
- });
197
- return cachedResponse ?? fetchedResponsePromise;
198
- }
199
- default: {
200
- return _handleRemoveDuplicate(options);
201
- }
202
- }
203
- }
204
- /**
205
- * Handle Remove Duplicates over `_handleRetryPattern`.
206
- */
207
- async function _handleRemoveDuplicate(options) {
208
- if (options.removeDuplicate === 'never')
209
- return _handleRetryPattern(options);
210
- logger.logMethod?.('_handleRemoveDuplicate');
211
- const cacheKey = options.method + ' ' + options.url;
212
- // We must cache fetch promise without await for handle other parallel requests.
213
- duplicateRequestStorage[cacheKey] ?? (duplicateRequestStorage[cacheKey] = _handleRetryPattern(options));
214
- try {
215
- // For all requests need to await for clone responses.
216
- const response = await duplicateRequestStorage[cacheKey];
217
- if (duplicateRequestStorage[cacheKey] != null) {
218
- if (response.ok !== true || options.removeDuplicate === 'until_load') {
219
- delete duplicateRequestStorage[cacheKey];
220
- }
221
- }
222
- return response.clone();
223
- }
224
- catch (err) {
225
- // clean cache on any error.
226
- delete duplicateRequestStorage[cacheKey];
227
- throw err;
228
- }
229
- }
230
- /**
231
- * Handle retry pattern over `_handleTimeout`.
232
- */
233
- async function _handleRetryPattern(options) {
234
- if (!(options.retry > 1))
235
- return _handleTimeout(options);
236
- logger.logMethod?.('_handleRetryPattern');
237
- options.retry--;
238
- const externalAbortSignal = options.signal;
239
- try {
240
- const response = await _handleTimeout(options);
241
- if (response.status < 500) {
242
- return response;
243
- }
244
- // else
245
- throw new Error('fetch_server_error');
246
- }
247
- catch (err) {
248
- logger.accident('fetch', 'fetch_failed_retry', err);
249
- if (globalScope.navigator?.onLine === false) {
250
- throw new Error('offline');
251
- }
252
- await delay(options.retryDelay);
253
- options.signal = externalAbortSignal;
254
- return _handleRetryPattern(options);
255
- }
256
- }
257
- /**
258
- * It's a wrapper around the browser's `fetch` with timeout.
259
- */
260
- function _handleTimeout(options) {
261
- if (options.timeout === 0) {
262
- return globalScope.fetch(options.url, options);
263
- }
264
- // else
265
- logger.logMethod?.('_handleTimeout');
266
- return new Promise((resolved, reject) => {
267
- const abortController = typeof globalScope.AbortController === 'function' ? new AbortController() : null;
268
- const externalAbortSignal = options.signal;
269
- options.signal = abortController?.signal;
270
- if (abortController !== null && externalAbortSignal != null) {
271
- // Respect external abort signal
272
- externalAbortSignal.addEventListener('abort', () => abortController.abort(), { once: true });
273
- }
274
- const timeoutId = setTimeout(() => {
275
- reject(new Error('fetch_timeout'));
276
- abortController?.abort('fetch_timeout');
277
- }, options.timeout);
278
- // abortController.signal.addEventListener('abort', () => {
279
- // logger.incident('fetch', 'fetch_abort_signal', {
280
- // reason: abortController.signal.reason,
281
- // });
282
- // });
283
- globalScope
284
- .fetch(options.url, options)
285
- .then((response) => resolved(response))
286
- .catch((reason) => reject(reason))
287
- .finally(() => {
288
- delete options.signal; // try to avoid memory leak in nodejs!
289
- clearTimeout(timeoutId);
290
- });
291
- });
292
- }
293
- //# sourceMappingURL=fetch.js.map
package/fetch.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"fetch.js","sourceRoot":"","sources":["src/fetch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,EAAC,MAAM,gBAAgB,CAAC;AACnF,OAAO,EAAC,WAAW,EAAE,KAAK,EAAC,MAAM,cAAc,CAAC;AAOhD,MAAM,MAAM,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;AAE5C,aAAa,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;AAE9B,IAAI,kBAAyB,CAAC;AAC9B,MAAM,cAAc,GAAG,QAAQ,IAAI,WAAW,CAAC;AAE/C,MAAM,uBAAuB,GAAsC,EAAE,CAAC;AAEtE;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,OAAqB;IAErB,MAAM,CAAC,aAAa,EAAE,CAAC,gBAAgB,EAAE,EAAC,GAAG,EAAE,OAAO,CAAC,GAAG,EAAC,CAAC,CAAC;IAE7D,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,OAAO,KAAf,OAAO,CAAC,OAAO,GAAK,EAAE,EAAC;QACvB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YAClC,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,WAAW,EAAE,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,IAAI,QAAkB,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;IAClC,CAAC;IACD,OAAO,GAAG,EAAE,CAAC;QACX,MAAM,UAAU,GAAI,GAAa,CAAC,OAAO,CAAC;QAC1C,IAAI,UAAU,KAAK,uBAAuB,EAAE,CAAC;YAC3C,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAG,GAAa,CAAC,OAAO,IAAI,cAAc,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;QACzF,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,YAAoB,CAAC;IACzB,IAAI,CAAC;QACH,YAAY,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;IACvC,CAAC;IACD,OAAO,GAAG,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,kBAAkB,EAAE,GAAG,EAAE;YACtD,QAAQ;SACT,CAAC,CAAC;QACH,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,YAAe,CAAC;IACpB,IAAI,CAAC;QACH,YAAY,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC1C,CAAC;IACD,OAAO,GAAG,EAAE,CAAC;QACX,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,cAAc,EAAE,GAAG,EAAE,EAAC,YAAY,EAAC,CAAC,CAAC;QACpE,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,YAAY,CAAC,EAAE,KAAK,IAAI,EAAE,CAAC;QAC7B,IAAI,OAAO,YAAY,CAAC,SAAS,KAAK,QAAQ,EAAE,CAAC;YAC/C,MAAM,CAAC,QAAQ,CAAC,gBAAgB,EAAE,YAAY,CAAC,SAAS,EAAE,EAAC,YAAY,EAAC,CAAC,CAAC;YAC1E,MAAM,IAAI,KAAK,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QAC1C,CAAC;aACI,CAAC;YACJ,MAAM,CAAC,KAAK,CAAC,gBAAgB,EAAE,WAAW,EAAE,EAAC,YAAY,EAAC,CAAC,CAAC;YAC5D,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;QAC/B,CAAC;IACH,CAAC;IAED,qFAAqF;IAErF,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,KAAK,CAAC,OAAqB;IACzC,OAAO,GAAG,eAAe,CAAC,OAAO,CAAC,CAAC;IACnC,MAAM,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,EAAC,OAAO,EAAC,CAAC,CAAC;IAC3C,OAAO,oBAAoB,CAAC,OAAiC,CAAC,CAAC;AACjE,CAAC;AAED;;GAEG;AACH,SAAS,eAAe,CAAC,OAAqB;IAC5C,OAAO,CAAC,MAAM,KAAd,OAAO,CAAC,MAAM,GAAK,KAAK,EAAC;IACzB,OAAO,CAAC,MAAM,KAAd,OAAO,CAAC,MAAM,GAAK,IAAI,EAAC;IAExB,OAAO,CAAC,OAAO,KAAf,OAAO,CAAC,OAAO,GAAK,KAAM,EAAC;IAC3B,OAAO,CAAC,KAAK,KAAb,OAAO,CAAC,KAAK,GAAK,CAAC,EAAC;IACpB,OAAO,CAAC,UAAU,KAAlB,OAAO,CAAC,UAAU,GAAK,IAAK,EAAC;IAC7B,OAAO,CAAC,aAAa,KAArB,OAAO,CAAC,aAAa,GAAK,cAAc,EAAC;IACzC,OAAO,CAAC,eAAe,KAAvB,OAAO,CAAC,eAAe,GAAK,OAAO,EAAC;IACpC,OAAO,CAAC,OAAO,KAAf,OAAO,CAAC,OAAO,GAAK,EAAE,EAAC;IAEvB,IAAI,OAAO,CAAC,aAAa,KAAK,cAAc,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;QACxE,MAAM,CAAC,QAAQ,EAAE,CAAC,OAAO,EAAE,kCAAkC,EAAE;YAC7D,cAAc;SACf,CAAC,CAAC;QACH,OAAO,CAAC,aAAa,GAAG,cAAc,CAAC;IACzC,CAAC;IAED,IAAI,OAAO,CAAC,eAAe,KAAK,MAAM,EAAE,CAAC;QACvC,OAAO,CAAC,eAAe,GAAG,cAAc,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC;IACrE,CAAC;IAED,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,IAAI,OAAO,CAAC,eAAe,IAAI,IAAI,EAAE,CAAC;QAC3E,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC;QAChD,kBAAkB;QAClB,MAAM,UAAU,GAAG,MAAM;aACtB,IAAI,CAAC,eAAe,CAAC;aACrB,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,GAAG,IAAI,MAAM,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;QAE1D,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC1B,OAAO,CAAC,GAAG,IAAI,GAAG,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;QAC7B,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAChD,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;IACvD,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,IAAI,IAAI,EAAE,CAAC;QAC1B,OAAO,CAAC,OAAO,CAAC,aAAa,GAAG,UAAU,OAAO,CAAC,KAAK,EAAE,CAAC;IAC5D,CAAC;IAED,IAAI,OAAO,CAAC,QAAQ,IAAI,IAAI,EAAE,CAAC;QAC7B,OAAO,CAAC,OAAO,CAAC,aAAa,GAAG,UAAU,OAAO,CAAC,QAAQ,CAAC,EAAE,IAAI,OAAO,CAAC,QAAQ,CAAC,KAAK,EAAE,CAAC;IAC5F,CAAC;IAED,OAAO,OAAiC,CAAC;AAC3C,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,oBAAoB,CAAC,OAA+B;IACjE,IAAI,OAAO,CAAC,aAAa,KAAK,cAAc,EAAE,CAAC;QAC7C,OAAO,sBAAsB,CAAC,OAAO,CAAC,CAAC;IACzC,CAAC;IACD,gCAAgC;IAChC,MAAM,CAAC,SAAS,EAAE,CAAC,sBAAsB,CAAC,CAAC;IAE3C,IAAI,kBAAkB,IAAI,IAAI,IAAI,OAAO,CAAC,gBAAgB,IAAI,IAAI,EAAE,CAAC;QACnE,kBAAkB,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IAC/D,CAAC;IAED,MAAM,YAAY,GAChB,OAAO,CAAC,gBAAgB,IAAI,IAAI,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,kBAAkB,CAAC;IAEtG,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IAElD,QAAQ,OAAO,CAAC,aAAa,EAAE,CAAC;QAC9B,KAAK,aAAa,CAAC,CAAC,CAAC;YACnB,MAAM,cAAc,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACzD,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;gBAC3B,OAAO,cAAc,CAAC;YACxB,CAAC;YACD,OAAO;YACP,MAAM,QAAQ,GAAG,MAAM,sBAAsB,CAAC,OAAO,CAAC,CAAC;YACvD,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAChB,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;YAC9C,CAAC;YACD,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,KAAK,YAAY,CAAC,CAAC,CAAC;YAClB,MAAM,cAAc,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACzD,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;gBAC3B,MAAM,CAAC,QAAQ,CAAC,sBAAsB,EAAE,uBAAuB,EAAE,EAAC,GAAG,EAAE,OAAO,CAAC,GAAG,EAAC,CAAC,CAAC;gBACrF,MAAM,IAAI,KAAK,CAAC,uBAAuB,CAAC,CAAC;YAC3C,CAAC;YACD,OAAO;YACP,OAAO,cAAc,CAAC;QACxB,CAAC;QAED,KAAK,eAAe,CAAC,CAAC,CAAC;YACrB,IAAI,CAAC;gBACH,MAAM,eAAe,GAAG,MAAM,sBAAsB,CAAC,OAAO,CAAC,CAAC;gBAC9D,IAAI,eAAe,CAAC,EAAE,EAAE,CAAC;oBACvB,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC;gBACrD,CAAC;gBACD,OAAO,eAAe,CAAC;YACzB,CAAC;YACD,OAAO,GAAG,EAAE,CAAC;gBACX,MAAM,cAAc,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBACzD,IAAI,cAAc,IAAI,IAAI,EAAE,CAAC;oBAC3B,OAAO,cAAc,CAAC;gBACxB,CAAC;gBACD,OAAO;gBACP,MAAM,GAAG,CAAC;YACZ,CAAC;QACH,CAAC;QAED,KAAK,cAAc,CAAC,CAAC,CAAC;YACpB,MAAM,eAAe,GAAG,MAAM,sBAAsB,CAAC,OAAO,CAAC,CAAC;YAC9D,IAAI,eAAe,CAAC,EAAE,EAAE,CAAC;gBACvB,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC;YACrD,CAAC;YACD,OAAO,eAAe,CAAC;QACzB,CAAC;QAED,KAAK,wBAAwB,CAAC,CAAC,CAAC;YAC9B,MAAM,cAAc,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YACzD,MAAM,sBAAsB,GAAG,sBAAsB,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,eAAe,EAAE,EAAE;gBACtF,IAAI,eAAe,CAAC,EAAE,EAAE,CAAC;oBACvB,YAAY,CAAC,GAAG,CAAC,OAAO,EAAE,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC;oBACnD,IAAI,OAAO,OAAO,CAAC,kBAAkB,KAAK,UAAU,EAAE,CAAC;wBACrD,UAAU,CAAC,OAAO,CAAC,kBAAkB,EAAE,CAAC,EAAE,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC;oBACrE,CAAC;gBACH,CAAC;gBACD,OAAO,eAAe,CAAC;YACzB,CAAC,CAAC,CAAC;YAEH,OAAO,cAAc,IAAI,sBAAsB,CAAC;QAClD,CAAC;QAED,OAAO,CAAC,CAAC,CAAC;YACR,OAAO,sBAAsB,CAAC,OAAO,CAAC,CAAC;QACzC,CAAC;IACH,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,sBAAsB,CAAC,OAA+B;IACnE,IAAI,OAAO,CAAC,eAAe,KAAK,OAAO;QAAE,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAE7E,MAAM,CAAC,SAAS,EAAE,CAAC,wBAAwB,CAAC,CAAC;IAE7C,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC;IAEpD,gFAAgF;IAChF,uBAAuB,CAAC,QAAQ,MAAhC,uBAAuB,CAAC,QAAQ,IAAM,mBAAmB,CAAC,OAAO,CAAC,EAAC;IAEnE,IAAI,CAAC;QACH,sDAAsD;QACtD,MAAM,QAAQ,GAAG,MAAM,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QAEzD,IAAI,uBAAuB,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;YAC9C,IAAI,QAAQ,CAAC,EAAE,KAAK,IAAI,IAAI,OAAO,CAAC,eAAe,KAAK,YAAY,EAAE,CAAC;gBACrE,OAAO,uBAAuB,CAAC,QAAQ,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC;QAED,OAAO,QAAQ,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;IACD,OAAO,GAAG,EAAE,CAAC;QACX,4BAA4B;QAC5B,OAAO,uBAAuB,CAAC,QAAQ,CAAC,CAAC;QACzC,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,KAAK,UAAU,mBAAmB,CAAC,OAA+B;IAChE,IAAI,CAAC,CAAC,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC;QAAE,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;IAEzD,MAAM,CAAC,SAAS,EAAE,CAAC,qBAAqB,CAAC,CAAC;IAC1C,OAAO,CAAC,KAAK,EAAE,CAAC;IAEhB,MAAM,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;IAE3C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;QAE/C,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;YAC1B,OAAO,QAAQ,CAAC;QAClB,CAAC;QACD,OAAO;QACP,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACxC,CAAC;IACD,OAAO,GAAG,EAAE,CAAC;QACX,MAAM,CAAC,QAAQ,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,CAAC,CAAC;QAEpD,IAAI,WAAW,CAAC,SAAS,EAAE,MAAM,KAAK,KAAK,EAAE,CAAC;YAC5C,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;QAED,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAEhC,OAAO,CAAC,MAAM,GAAG,mBAAmB,CAAC;QACrC,OAAO,mBAAmB,CAAC,OAAO,CAAC,CAAC;IACtC,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,OAAqB;IAC3C,IAAI,OAAO,CAAC,OAAO,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,WAAW,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;IACjD,CAAC;IACD,OAAO;IACP,MAAM,CAAC,SAAS,EAAE,CAAC,gBAAgB,CAAC,CAAC;IACrC,OAAO,IAAI,OAAO,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE;QACtC,MAAM,eAAe,GAAG,OAAO,WAAW,CAAC,eAAe,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,eAAe,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;QACzG,MAAM,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC;QAC3C,OAAO,CAAC,MAAM,GAAG,eAAe,EAAE,MAAM,CAAC;QAEzC,IAAI,eAAe,KAAK,IAAI,IAAI,mBAAmB,IAAI,IAAI,EAAE,CAAC;YAC5D,gCAAgC;YAChC,mBAAmB,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,KAAK,EAAE,EAAE,EAAC,IAAI,EAAE,IAAI,EAAC,CAAC,CAAC;QAC7F,CAAC;QAED,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC;YACnC,eAAe,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;QAC1C,CAAC,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAEpB,2DAA2D;QAC3D,qDAAqD;QACrD,6CAA6C;QAC7C,QAAQ;QACR,MAAM;QAEN,WAAW;aACR,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC;aAC3B,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;aACtC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;aACjC,OAAO,CAAC,GAAG,EAAE;YACZ,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,sCAAsC;YAC7D,YAAY,CAAC,SAAS,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import {createLogger, definePackage, NODE_MODE, globalScope} from '@alwatr/logger';\nimport {getClientId, delay} from '@alwatr/util';\n\nimport type {FetchOptions} from './type.js';\nimport type {AlwatrServiceResponse} from '@alwatr/type';\n\nexport type * from './type.js';\n\nconst logger = createLogger('alwatr/fetch');\n\ndefinePackage('fetch', '2.x');\n\nlet alwatrCacheStorage: Cache;\nconst cacheSupported = 'caches' in globalScope;\n\nconst duplicateRequestStorage: Record<string, Promise<Response>> = {};\n\n/**\n * Fetch from alwatr services and return standard response.\n */\nexport async function serviceRequest<T extends AlwatrServiceResponse = AlwatrServiceResponse>(\n options: FetchOptions,\n): Promise<T> {\n logger.logMethodArgs?.('serviceRequest', {url: options.url});\n\n if (!NODE_MODE) {\n options.headers ??= {};\n if (!options.headers['client-id']) {\n options.headers['client-id'] = getClientId();\n }\n }\n\n let response: Response;\n try {\n response = await fetch(options);\n }\n catch (err) {\n const errMessage = (err as Error).message;\n if (errMessage !== 'fetch_cache_not_found') {\n logger.error('serviceRequest', (err as Error).message || 'fetch_failed', err, options);\n }\n throw err;\n }\n\n let responseText: string;\n try {\n responseText = await response.text();\n }\n catch (err) {\n logger.error('serviceRequest', 'invalid_response', err, {\n response,\n });\n throw err;\n }\n\n let responseJson: T;\n try {\n responseJson = JSON.parse(responseText);\n }\n catch (err) {\n logger.error('serviceRequest', 'invalid_json', err, {responseText});\n throw err;\n }\n\n if (responseJson.ok !== true) {\n if (typeof responseJson.errorCode === 'string') {\n logger.accident('serviceRequest', responseJson.errorCode, {responseJson});\n throw new Error(responseJson.errorCode);\n }\n else {\n logger.error('serviceRequest', 'fetch_nok', {responseJson});\n throw new Error('fetch_nok');\n }\n }\n\n // TODO: generate fetch signals hook (for easier handle loading and show error toast)\n\n return responseJson;\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 * Example:\n *\n * ```ts\n * const response = await fetch({\n * url: '/api/products',\n * queryParameters: {limit: 10},\n * timeout: 10_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\n/**\n * Process fetch options and set defaults, etc.\n */\nfunction _processOptions(options: FetchOptions): Required<FetchOptions> {\n options.method ??= 'GET';\n options.window ??= null;\n\n options.timeout ??= 10_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.queryParameters != null) {\n const queryParameters = options.queryParameters;\n // prettier-ignore\n const queryArray = Object\n .keys(queryParameters)\n .map((key) => `${key}=${String(queryParameters[key])}`);\n\n if (queryArray.length > 0) {\n options.url += '?' + queryArray.join('&');\n }\n }\n\n if (options.bodyJson != null) {\n options.body = JSON.stringify(options.bodyJson);\n options.headers['Content-Type'] = 'application/json';\n }\n\n if (options.token != null) {\n options.headers.Authorization = `Bearer ${options.token}`;\n }\n\n if (options.userAuth != null) {\n options.headers.Authorization = `Bearer ${options.userAuth.id}/${options.userAuth.token}`;\n }\n\n return options as Required<FetchOptions>;\n}\n\n/**\n * Handle Cache Strategy over `_handleRemoveDuplicate`.\n */\nasync 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 (alwatrCacheStorage == null && options.cacheStorageName == null) {\n alwatrCacheStorage = await caches.open('alwatr_fetch_cache');\n }\n\n const cacheStorage =\n options.cacheStorageName != null ? await caches.open(options.cacheStorageName) : alwatrCacheStorage;\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 */\nasync 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 */\nasync 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 < 500) {\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 (globalScope.navigator?.onLine === false) {\n throw new Error('offline');\n }\n\n await delay(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 */\nfunction _handleTimeout(options: FetchOptions): Promise<Response> {\n if (options.timeout === 0) {\n return globalScope.fetch(options.url, options);\n }\n // else\n logger.logMethod?.('_handleTimeout');\n return new Promise((resolved, reject) => {\n const abortController = typeof globalScope.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 }, options.timeout);\n\n // abortController.signal.addEventListener('abort', () => {\n // logger.incident('fetch', 'fetch_abort_signal', {\n // reason: abortController.signal.reason,\n // });\n // });\n\n globalScope\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"]}