Skip to content

Commit a289d8f

Browse files
authored
Merge pull request #427 from AliMD/feat/fetch
feat(token): new token option & improve types
2 parents e504443 + ccf62f4 commit a289d8f

File tree

4 files changed

+159
-138
lines changed

4 files changed

+159
-138
lines changed

packages/core/fetch/src/fetch.ts

+68-134
Original file line numberDiff line numberDiff line change
@@ -1,96 +1,16 @@
11
import {createLogger, alwatrRegisteredList} from '@alwatr/logger';
22

3+
import type {FetchOptions, CacheDuplicate, CacheStrategy} from './type';
4+
5+
export {FetchOptions, CacheDuplicate, CacheStrategy};
6+
37
const logger = createLogger('alwatr/fetch');
48

59
alwatrRegisteredList.push({
610
name: '@alwatr/fetch',
711
version: '{{ALWATR_VERSION}}',
812
});
913

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-
9414
let alwatrCacheStorage: Cache;
9515
const cacheSupported = 'caches' in globalThis;
9616

@@ -113,7 +33,7 @@ const duplicateRequestStorage: Record<string, Promise<Response>> = {};
11333
* });
11434
* ```
11535
*/
116-
export function fetch(_options: Partial<FetchOptions> & {url: string}): Promise<Response> {
36+
export function fetch(_options: FetchOptions): Promise<Response> {
11737
const options = _processOptions(_options);
11838
logger.logMethodArgs('fetch', {options});
11939
return _handleCacheStrategy(options);
@@ -122,7 +42,7 @@ export function fetch(_options: Partial<FetchOptions> & {url: string}): Promise<
12242
/**
12343
* Process fetch options and set defaults, etc.
12444
*/
125-
function _processOptions(options: Partial<FetchOptions> & {url: string}): FetchOptions {
45+
function _processOptions(options: FetchOptions): Required<FetchOptions> {
12646
options.method = options.method != null ? options.method.toUpperCase() : 'GET';
12747
options.window ??= null;
12848

@@ -163,46 +83,20 @@ function _processOptions(options: Partial<FetchOptions> & {url: string}): FetchO
16383
};
16484
}
16585

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+
};
19991
}
92+
93+
return options as Required<FetchOptions>;
20094
}
20195

20296
/**
20397
* Handle Cache Strategy over `_handleRemoveDuplicate`.
20498
*/
205-
async function _handleCacheStrategy(options: FetchOptions): Promise<Response> {
99+
async function _handleCacheStrategy(options: Required<FetchOptions>): Promise<Response> {
206100
if (options.cacheStrategy === 'network_only') {
207101
return _handleRemoveDuplicate(options);
208102
}
@@ -221,7 +115,10 @@ async function _handleCacheStrategy(options: FetchOptions): Promise<Response> {
221115
switch (options.cacheStrategy) {
222116
case 'cache_first': {
223117
const cachedResponse = await cacheStorage.match(request);
224-
if (cachedResponse != null) return cachedResponse;
118+
if (cachedResponse != null) {
119+
return cachedResponse;
120+
}
121+
// else
225122
const response = await _handleRemoveDuplicate(options);
226123
if (response.ok) {
227124
cacheStorage.put(request, response.clone());
@@ -231,8 +128,11 @@ async function _handleCacheStrategy(options: FetchOptions): Promise<Response> {
231128

232129
case 'cache_only': {
233130
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');
236136
}
237137

238138
case 'network_first': {
@@ -245,25 +145,27 @@ async function _handleCacheStrategy(options: FetchOptions): Promise<Response> {
245145
}
246146
catch (err) {
247147
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;
250153
}
251154
}
252155

253156
case 'stale_while_revalidate': {
254157
const cachedResponse = await cacheStorage.match(request);
255-
const fetchedResponsePromise = _handleRemoveDuplicate(options);
256-
257-
fetchedResponsePromise.then((networkResponse) => {
158+
const fetchedResponsePromise = _handleRemoveDuplicate(options).then((networkResponse) => {
258159
if (networkResponse.ok) {
259160
cacheStorage.put(request, networkResponse.clone());
260161
if (cachedResponse != null && typeof options.revalidateCallback === 'function') {
261162
options.revalidateCallback(networkResponse);
262163
}
263164
}
165+
return networkResponse;
264166
});
265167

266-
return cachedResponse || fetchedResponsePromise;
168+
return cachedResponse ?? fetchedResponsePromise;
267169
}
268170

269171
default: {
@@ -272,10 +174,42 @@ async function _handleCacheStrategy(options: FetchOptions): Promise<Response> {
272174
}
273175
}
274176

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+
275209
/**
276210
* Handle retry pattern over `_handleTimeout`.
277211
*/
278-
async function _handleRetryPattern(options: FetchOptions): Promise<Response> {
212+
async function _handleRetryPattern(options: Required<FetchOptions>): Promise<Response> {
279213
if (!(options.retry > 1)) return _handleTimeout(options);
280214

281215
logger.logMethod('_handleRetryPattern');
@@ -286,11 +220,11 @@ async function _handleRetryPattern(options: FetchOptions): Promise<Response> {
286220
try {
287221
const response = await _handleTimeout(options);
288222

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;
292225
}
293-
else return response;
226+
// else
227+
throw new Error('fetch_server_error');
294228
}
295229
catch (err) {
296230
logger.accident('fetch', (err as Error)?.name ?? 'fetch_failed', 'fetch failed and retry', {err});

packages/core/fetch/src/type.ts

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
2+
export type CacheStrategy = 'network_only' | 'network_first' | 'cache_only' | 'cache_first' | 'stale_while_revalidate';
3+
export type CacheDuplicate = 'never' | 'always' | 'until_load' | 'auto';
4+
5+
export interface FetchOptions extends RequestInit {
6+
/**
7+
* Request URL.
8+
*/
9+
url: string;
10+
11+
/**
12+
* A string to set request's method.
13+
*/
14+
method?: string;
15+
16+
/**
17+
* A timeout for the fetch request.
18+
* Set `0` for disable it.
19+
*
20+
* Use with cation, you will have memory leak issue in nodejs.
21+
*
22+
* @default 10_000 ms
23+
*/
24+
timeout?: number;
25+
26+
/**
27+
* If fetch response not acceptable or timed out, it will retry the request.
28+
*
29+
* @default 3
30+
*/
31+
retry?: number;
32+
33+
/**
34+
* Delay before each retries.
35+
*
36+
* @default 1_000 ms
37+
*/
38+
retryDelay?: number;
39+
40+
/**
41+
* Simple memory caching for remove duplicate/parallel requests.
42+
*
43+
* - `never`: Never use memory caching.
44+
* - `always`: Always use memory caching and remove all duplicate requests.
45+
* - `until_load`: Cache parallel requests until request completed (it will be removed after the promise resolved).
46+
* - `auto`: If CacheStorage was supported use `until_load` strategy else use `always`.
47+
*
48+
* @default 'never'
49+
*/
50+
removeDuplicate?: CacheDuplicate;
51+
52+
/**
53+
* Strategies for caching.
54+
*
55+
* - `network_only`: Only network request without any cache.
56+
* - `network_first`: Network first, falling back to cache.
57+
* - `cache_only`: Cache only without any network request.
58+
* - `cache_first`: Cache first, falling back to network.
59+
* - `stale_while_revalidate`: Fastest strategy, Use cached first but always request network to update the cache.
60+
*
61+
* @default 'network_only'
62+
*/
63+
cacheStrategy?: CacheStrategy;
64+
65+
/**
66+
* Revalidate callback for `stale_while_revalidate` cache strategy.
67+
*/
68+
revalidateCallback?: (response: Response) => void;
69+
70+
/**
71+
* Cache storage custom name.
72+
*/
73+
cacheStorageName?: string;
74+
75+
/**
76+
* Body as JS Object.
77+
*/
78+
bodyJson?: Record<string | number, unknown>;
79+
80+
/**
81+
* URL Query Parameters as JS Object.
82+
*/
83+
queryParameters?: Record<string, string | number | boolean>;
84+
85+
/**
86+
* Add token to Authentication bearer header.
87+
*/
88+
token?: string;
89+
}

packages/core/nano-server/src/nano-server.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ export class AlwatrConnection {
327327
getToken(): string | null {
328328
const auth = this.incomingMessage.headers.authorization?.split(' ');
329329

330-
if (auth == null || auth[0] !== 'Bearer') {
330+
if (auth == null || auth[0].toLowerCase() !== 'bearer') {
331331
return null;
332332
}
333333

0 commit comments

Comments
 (0)