1
1
import { createLogger , alwatrRegisteredList } from '@alwatr/logger' ;
2
2
3
+ import type { FetchOptions , CacheDuplicate , CacheStrategy } from './type' ;
4
+
5
+ export { FetchOptions , CacheDuplicate , CacheStrategy } ;
6
+
3
7
const logger = createLogger ( 'alwatr/fetch' ) ;
4
8
5
9
alwatrRegisteredList . push ( {
6
10
name : '@alwatr/fetch' ,
7
11
version : '{{ALWATR_VERSION}}' ,
8
12
} ) ;
9
13
10
- export type CacheStrategy = 'network_only' | 'network_first' | 'cache_only' | 'cache_first' | 'stale_while_revalidate' ;
11
- export type CacheDuplicate = 'never' | 'always' | 'until_load' | 'auto' ;
12
-
13
- export interface FetchOptions extends RequestInit {
14
- /**
15
- * Request URL.
16
- */
17
- url : string ;
18
-
19
- /**
20
- * A string to set request's method.
21
- */
22
- method : string ;
23
-
24
- /**
25
- * A timeout for the fetch request.
26
- * Set `0` for disable it.
27
- *
28
- * Use with cation, you will have memory leak issue in nodejs.
29
- *
30
- * @default 10_000 ms
31
- */
32
- timeout : number ;
33
-
34
- /**
35
- * If fetch response not acceptable or timed out, it will retry the request.
36
- *
37
- * @default 3
38
- */
39
- retry : number ;
40
-
41
- /**
42
- * Delay before each retries.
43
- *
44
- * @default 1_000 ms
45
- */
46
- retryDelay : number ;
47
-
48
- /**
49
- * Simple memory caching for remove duplicate/parallel requests.
50
- *
51
- * - `never`: Never use memory caching.
52
- * - `always`: Always use memory caching and remove all duplicate requests.
53
- * - `until_load`: Cache parallel requests until request completed (it will be removed after the promise resolved).
54
- * - `auto`: If CacheStorage was supported use `until_load` strategy else use `always`.
55
- *
56
- * @default 'never'
57
- */
58
- removeDuplicate : CacheDuplicate ;
59
-
60
- /**
61
- * Strategies for caching.
62
- *
63
- * - `network_only`: Only network request without any cache.
64
- * - `network_first`: Network first, falling back to cache.
65
- * - `cache_only`: Cache only without any network request.
66
- * - `cache_first`: Cache first, falling back to network.
67
- * - `stale_while_revalidate`: Fastest strategy, Use cached first but always request network to update the cache.
68
- *
69
- * @default 'network_only'
70
- */
71
- cacheStrategy : CacheStrategy ;
72
-
73
- /**
74
- * Revalidate callback for `stale_while_revalidate` cache strategy.
75
- */
76
- revalidateCallback ?: ( response : Response ) => void ;
77
-
78
- /**
79
- * Cache storage custom name.
80
- */
81
- cacheStorageName ?: string ;
82
-
83
- /**
84
- * Body as JS Object.
85
- */
86
- bodyJson ?: Record < string | number , unknown > ;
87
-
88
- /**
89
- * URL Query Parameters as JS Object.
90
- */
91
- queryParameters ?: Record < string , string | number | boolean > ;
92
- }
93
-
94
14
let alwatrCacheStorage : Cache ;
95
15
const cacheSupported = 'caches' in globalThis ;
96
16
@@ -113,7 +33,7 @@ const duplicateRequestStorage: Record<string, Promise<Response>> = {};
113
33
* });
114
34
* ```
115
35
*/
116
- export function fetch ( _options : Partial < FetchOptions > & { url : string } ) : Promise < Response > {
36
+ export function fetch ( _options : FetchOptions ) : Promise < Response > {
117
37
const options = _processOptions ( _options ) ;
118
38
logger . logMethodArgs ( 'fetch' , { options} ) ;
119
39
return _handleCacheStrategy ( options ) ;
@@ -122,7 +42,7 @@ export function fetch(_options: Partial<FetchOptions> & {url: string}): Promise<
122
42
/**
123
43
* Process fetch options and set defaults, etc.
124
44
*/
125
- function _processOptions ( options : Partial < FetchOptions > & { url : string } ) : FetchOptions {
45
+ function _processOptions ( options : FetchOptions ) : Required < FetchOptions > {
126
46
options . method = options . method != null ? options . method . toUpperCase ( ) : 'GET' ;
127
47
options . window ??= null ;
128
48
@@ -163,46 +83,20 @@ function _processOptions(options: Partial<FetchOptions> & {url: string}): FetchO
163
83
} ;
164
84
}
165
85
166
- return options as FetchOptions ;
167
- }
168
-
169
- /**
170
- * Handle Remove Duplicates over `_handleRetryPattern`.
171
- */
172
- async function _handleRemoveDuplicate ( options : FetchOptions ) : Promise < Response > {
173
- if ( options . removeDuplicate === 'never' ) return _handleRetryPattern ( options ) ;
174
-
175
- logger . logMethod ( '_handleRemoveDuplicate' ) ;
176
-
177
- const cacheKey = `[${ options . method } ] ${ options . url } ` ;
178
- const firstRequest = duplicateRequestStorage [ cacheKey ] == null ;
179
-
180
- // We must cache fetch promise without await for handle other parallel requests.
181
- duplicateRequestStorage [ cacheKey ] ??= _handleRetryPattern ( options ) ;
182
-
183
- try {
184
- // For all requests need to await for clone responses.
185
- const response = await duplicateRequestStorage [ cacheKey ] ;
186
-
187
- if ( firstRequest === true ) {
188
- if ( response . ok !== true || options . removeDuplicate === 'until_load' ) {
189
- delete duplicateRequestStorage [ cacheKey ] ;
190
- }
191
- }
192
-
193
- return response . clone ( ) ;
194
- }
195
- catch ( err ) {
196
- // clean cache on any error.
197
- delete duplicateRequestStorage [ cacheKey ] ;
198
- throw err ;
86
+ if ( options . token != null ) {
87
+ options . headers = {
88
+ ...options . headers ,
89
+ Authorization : `Bearer ${ options . token } ` ,
90
+ } ;
199
91
}
92
+
93
+ return options as Required < FetchOptions > ;
200
94
}
201
95
202
96
/**
203
97
* Handle Cache Strategy over `_handleRemoveDuplicate`.
204
98
*/
205
- async function _handleCacheStrategy ( options : FetchOptions ) : Promise < Response > {
99
+ async function _handleCacheStrategy ( options : Required < FetchOptions > ) : Promise < Response > {
206
100
if ( options . cacheStrategy === 'network_only' ) {
207
101
return _handleRemoveDuplicate ( options ) ;
208
102
}
@@ -221,7 +115,10 @@ async function _handleCacheStrategy(options: FetchOptions): Promise<Response> {
221
115
switch ( options . cacheStrategy ) {
222
116
case 'cache_first' : {
223
117
const cachedResponse = await cacheStorage . match ( request ) ;
224
- if ( cachedResponse != null ) return cachedResponse ;
118
+ if ( cachedResponse != null ) {
119
+ return cachedResponse ;
120
+ }
121
+ // else
225
122
const response = await _handleRemoveDuplicate ( options ) ;
226
123
if ( response . ok ) {
227
124
cacheStorage . put ( request , response . clone ( ) ) ;
@@ -231,8 +128,11 @@ async function _handleCacheStrategy(options: FetchOptions): Promise<Response> {
231
128
232
129
case 'cache_only' : {
233
130
const cachedResponse = await cacheStorage . match ( request ) ;
234
- if ( cachedResponse == null ) throw new Error ( 'fetch_cache_not_found' ) ;
235
- return cachedResponse ;
131
+ if ( cachedResponse != null ) {
132
+ return cachedResponse ;
133
+ }
134
+ // else
135
+ throw new Error ( 'fetch_cache_not_found' ) ;
236
136
}
237
137
238
138
case 'network_first' : {
@@ -245,25 +145,27 @@ async function _handleCacheStrategy(options: FetchOptions): Promise<Response> {
245
145
}
246
146
catch ( err ) {
247
147
const cachedResponse = await cacheStorage . match ( request ) ;
248
- if ( cachedResponse == null ) throw err ;
249
- return cachedResponse ;
148
+ if ( cachedResponse != null ) {
149
+ return cachedResponse ;
150
+ }
151
+ // else
152
+ throw err ;
250
153
}
251
154
}
252
155
253
156
case 'stale_while_revalidate' : {
254
157
const cachedResponse = await cacheStorage . match ( request ) ;
255
- const fetchedResponsePromise = _handleRemoveDuplicate ( options ) ;
256
-
257
- fetchedResponsePromise . then ( ( networkResponse ) => {
158
+ const fetchedResponsePromise = _handleRemoveDuplicate ( options ) . then ( ( networkResponse ) => {
258
159
if ( networkResponse . ok ) {
259
160
cacheStorage . put ( request , networkResponse . clone ( ) ) ;
260
161
if ( cachedResponse != null && typeof options . revalidateCallback === 'function' ) {
261
162
options . revalidateCallback ( networkResponse ) ;
262
163
}
263
164
}
165
+ return networkResponse ;
264
166
} ) ;
265
167
266
- return cachedResponse || fetchedResponsePromise ;
168
+ return cachedResponse ?? fetchedResponsePromise ;
267
169
}
268
170
269
171
default : {
@@ -272,10 +174,42 @@ async function _handleCacheStrategy(options: FetchOptions): Promise<Response> {
272
174
}
273
175
}
274
176
177
+ /**
178
+ * Handle Remove Duplicates over `_handleRetryPattern`.
179
+ */
180
+ async function _handleRemoveDuplicate ( options : Required < FetchOptions > ) : Promise < Response > {
181
+ if ( options . removeDuplicate === 'never' ) return _handleRetryPattern ( options ) ;
182
+
183
+ logger . logMethod ( '_handleRemoveDuplicate' ) ;
184
+
185
+ const cacheKey = options . method + ' ' + options . url ;
186
+
187
+ // We must cache fetch promise without await for handle other parallel requests.
188
+ duplicateRequestStorage [ cacheKey ] ??= _handleRetryPattern ( options ) ;
189
+
190
+ try {
191
+ // For all requests need to await for clone responses.
192
+ const response = await duplicateRequestStorage [ cacheKey ] ;
193
+
194
+ if ( duplicateRequestStorage [ cacheKey ] != null ) {
195
+ if ( response . ok !== true || options . removeDuplicate === 'until_load' ) {
196
+ delete duplicateRequestStorage [ cacheKey ] ;
197
+ }
198
+ }
199
+
200
+ return response . clone ( ) ;
201
+ }
202
+ catch ( err ) {
203
+ // clean cache on any error.
204
+ delete duplicateRequestStorage [ cacheKey ] ;
205
+ throw err ;
206
+ }
207
+ }
208
+
275
209
/**
276
210
* Handle retry pattern over `_handleTimeout`.
277
211
*/
278
- async function _handleRetryPattern ( options : FetchOptions ) : Promise < Response > {
212
+ async function _handleRetryPattern ( options : Required < FetchOptions > ) : Promise < Response > {
279
213
if ( ! ( options . retry > 1 ) ) return _handleTimeout ( options ) ;
280
214
281
215
logger . logMethod ( '_handleRetryPattern' ) ;
@@ -286,11 +220,11 @@ async function _handleRetryPattern(options: FetchOptions): Promise<Response> {
286
220
try {
287
221
const response = await _handleTimeout ( options ) ;
288
222
289
- if ( response . status >= 500 ) {
290
- logger . incident ( 'fetch' , 'fetch_server_error' , 'fetch server error ' + response . status ) ;
291
- throw new Error ( 'fetch_server_error' ) ;
223
+ if ( response . status < 500 ) {
224
+ return response ;
292
225
}
293
- else return response ;
226
+ // else
227
+ throw new Error ( 'fetch_server_error' ) ;
294
228
}
295
229
catch ( err ) {
296
230
logger . accident ( 'fetch' , ( err as Error ) ?. name ?? 'fetch_failed' , 'fetch failed and retry' , { err} ) ;
0 commit comments