lissa 1.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/LICENSE +21 -0
- package/README.md +715 -0
- package/lib/core/defaults.js +22 -0
- package/lib/core/errors.js +13 -0
- package/lib/core/lissa.js +182 -0
- package/lib/core/request.js +488 -0
- package/lib/exports.js +3 -0
- package/lib/index.d.ts +510 -0
- package/lib/index.js +28 -0
- package/lib/plugins/dedupe.js +45 -0
- package/lib/plugins/index.js +2 -0
- package/lib/plugins/retry.js +65 -0
- package/lib/utils/OpenPromise.js +88 -0
- package/lib/utils/helper.js +195 -0
- package/package.json +72 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
import { RequestInit, HeadersInit, BodyInit, Headers } from 'undici-types';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* General json typing
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
type JsonPrimitive = null | string | number | boolean;
|
|
8
|
+
|
|
9
|
+
type JsonObject = {
|
|
10
|
+
[key: string]: Json;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type JsonArray = Json[];
|
|
14
|
+
|
|
15
|
+
type Json = JsonPrimitive | JsonObject | JsonArray;
|
|
16
|
+
|
|
17
|
+
/*
|
|
18
|
+
* Param typing (like json but it supports dates if paramsSerializer is set to extended mode)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
type ParamPrimitive = null | string | number | bigint | boolean | Date;
|
|
22
|
+
|
|
23
|
+
type ParamObject = {
|
|
24
|
+
[key: string]: ParamValue;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type ParamArray = ParamValue[];
|
|
28
|
+
|
|
29
|
+
type ParamValue = ParamPrimitive | ParamObject | ParamArray;
|
|
30
|
+
|
|
31
|
+
type Params = ParamObject;
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* Input and output types
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | (string & {});
|
|
38
|
+
|
|
39
|
+
type LissaOptionsInit = Omit<RequestInit, 'method' | 'body'> & {
|
|
40
|
+
adapter?: 'fetch' | 'xhr';
|
|
41
|
+
baseURL?: string;
|
|
42
|
+
url?: string;
|
|
43
|
+
method?: HttpMethod;
|
|
44
|
+
authenticate?: { username: string, password: string };
|
|
45
|
+
params?: Params;
|
|
46
|
+
paramsSerializer?: 'simple' | 'extended' | ((params: Params) => string);
|
|
47
|
+
urlBuilder?: 'simple' | 'extended' | ((url: string, baseURL: string) => string | URL);
|
|
48
|
+
responseType?: 'json' | 'text' | 'file' | 'raw';
|
|
49
|
+
timeout?: number;
|
|
50
|
+
onUploadProgress?: (uploaded: number, total: number) => void;
|
|
51
|
+
onDownloadProgress?: (downloaded: number, total: number) => void;
|
|
52
|
+
body?: BodyInit | JsonObject;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type LissaOptions = Omit<LissaOptionsInit, 'headers'> & {
|
|
56
|
+
headers: Headers
|
|
57
|
+
params: Params;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
type DefaultOptionsInit = LissaOptionsInit & {
|
|
61
|
+
get?: LissaOptionsInit;
|
|
62
|
+
post?: LissaOptionsInit;
|
|
63
|
+
put?: LissaOptionsInit;
|
|
64
|
+
patch?: LissaOptionsInit;
|
|
65
|
+
delete?: LissaOptionsInit;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type DefaultOptions = LissaOptions & {
|
|
69
|
+
get: LissaOptions;
|
|
70
|
+
post: LissaOptions;
|
|
71
|
+
put: LissaOptions;
|
|
72
|
+
patch: LissaOptions;
|
|
73
|
+
delete: LissaOptions;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
type FetchArguments = {
|
|
77
|
+
url: URL,
|
|
78
|
+
options: Omit<RequestInit, 'headers'> & { headers: Headers },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
type LissaResult = {
|
|
82
|
+
options: LissaOptions;
|
|
83
|
+
request: FetchArguments;
|
|
84
|
+
response: Response;
|
|
85
|
+
headers: Headers;
|
|
86
|
+
status: number;
|
|
87
|
+
data: null | string | Json | File | ReadableStream | Blob;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
type GeneralErrorResponse = Error & {
|
|
91
|
+
options: LissaOptions;
|
|
92
|
+
request: FetchArguments;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
type ResultValue = LissaResult | Exclude<any, undefined>;
|
|
96
|
+
|
|
97
|
+
/*
|
|
98
|
+
* Interfaces
|
|
99
|
+
*/
|
|
100
|
+
|
|
101
|
+
declare class LissaRequest extends Promise<ResultValue> {
|
|
102
|
+
readonly options: LissaOptions;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Set a base URL for the request
|
|
106
|
+
*/
|
|
107
|
+
baseURL(baseURL: string): LissaRequest;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Set the request URL
|
|
111
|
+
*/
|
|
112
|
+
url(url: string): LissaRequest;
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Set the HTTP method (GET, POST, etc.)
|
|
116
|
+
*/
|
|
117
|
+
method(method: HttpMethod): LissaRequest;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Add or override request headers
|
|
121
|
+
*/
|
|
122
|
+
headers(headers: HeadersInit): LissaRequest;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Provide basic authentication credentials
|
|
126
|
+
*
|
|
127
|
+
* It sets the "Authorization" header to "Basic base64(username:password)"
|
|
128
|
+
*/
|
|
129
|
+
authenticate(username: string, password: string): LissaRequest;
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Add or override query string parameters
|
|
133
|
+
*
|
|
134
|
+
* Check the paramsSerializer option to control the serialization of the params
|
|
135
|
+
*/
|
|
136
|
+
params(params: Params): LissaRequest;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Attach or merge a request body
|
|
140
|
+
*
|
|
141
|
+
* The body gets json stringified if it is a plain object
|
|
142
|
+
*/
|
|
143
|
+
body(body: BodyInit | JsonObject): LissaRequest;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Set request timeout in milliseconds
|
|
147
|
+
*
|
|
148
|
+
* Attaches an AbortSignal.timeout(...) signal to the request
|
|
149
|
+
*/
|
|
150
|
+
timeout(timeout: number): LissaRequest;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Attach an AbortSignal to cancel the request
|
|
154
|
+
*/
|
|
155
|
+
signal(signal: AbortSignal): LissaRequest;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Change the expected response type
|
|
159
|
+
*/
|
|
160
|
+
responseType(responseType: 'json' | 'text' | 'file' | 'raw'): LissaRequest;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Add an upload progress listener
|
|
164
|
+
*/
|
|
165
|
+
onUploadProgress(onProgress: (uploaded: number, total: number) => void): LissaRequest;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Add a download progress listener
|
|
169
|
+
*/
|
|
170
|
+
onDownloadProgress(onProgress: (downloaded: number, total: number) => void): LissaRequest;
|
|
171
|
+
|
|
172
|
+
readonly status: 'pending' | 'fulfilled' | 'rejected';
|
|
173
|
+
readonly value: void | ResultValue;
|
|
174
|
+
readonly reason: void | Error;
|
|
175
|
+
|
|
176
|
+
on(
|
|
177
|
+
event: 'resolve' | 'reject' | 'settle',
|
|
178
|
+
listener: (arg: ResultValue | Error | {
|
|
179
|
+
status: 'fulfilled' | 'rejected',
|
|
180
|
+
value: void | ResultValue,
|
|
181
|
+
reason: void | Error,
|
|
182
|
+
}) => void,
|
|
183
|
+
): LissaRequest;
|
|
184
|
+
|
|
185
|
+
off(
|
|
186
|
+
event: 'resolve' | 'reject' | 'settle',
|
|
187
|
+
listener: (arg: ResultValue | Error | {
|
|
188
|
+
status: 'fulfilled' | 'rejected',
|
|
189
|
+
value: void | ResultValue,
|
|
190
|
+
reason: void | Error,
|
|
191
|
+
}) => void,
|
|
192
|
+
): LissaRequest;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
interface MakeRequest {
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Perform a GET request
|
|
199
|
+
*/
|
|
200
|
+
get(
|
|
201
|
+
url?: string,
|
|
202
|
+
options?: Omit<LissaOptionsInit, 'method' | 'url'>
|
|
203
|
+
): LissaRequest;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Perform a POST request with optional body
|
|
207
|
+
*/
|
|
208
|
+
post(
|
|
209
|
+
url?: string,
|
|
210
|
+
body?: BodyInit | JsonObject,
|
|
211
|
+
options?: Omit<LissaOptionsInit, 'method' | 'url' | 'body'>
|
|
212
|
+
): LissaRequest;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Perform a PUT request with optional body
|
|
216
|
+
*/
|
|
217
|
+
put(
|
|
218
|
+
url?: string,
|
|
219
|
+
body?: BodyInit | JsonObject,
|
|
220
|
+
options?: Omit<LissaOptionsInit, 'method' | 'url' | 'body'>
|
|
221
|
+
): LissaRequest;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Perform a PATCH request with optional body
|
|
225
|
+
*/
|
|
226
|
+
patch(
|
|
227
|
+
url?: string,
|
|
228
|
+
body?: BodyInit | JsonObject,
|
|
229
|
+
options?: Omit<LissaOptionsInit, 'method' | 'url' | 'body'>
|
|
230
|
+
): LissaRequest;
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Perform a DELETE request
|
|
234
|
+
*/
|
|
235
|
+
delete(
|
|
236
|
+
url?: string,
|
|
237
|
+
options?: Omit<LissaOptionsInit, 'method' | 'url'>
|
|
238
|
+
): LissaRequest;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Perform a general fetch request.
|
|
242
|
+
*
|
|
243
|
+
* Specify url, method, body, headers and more in the given options object
|
|
244
|
+
*/
|
|
245
|
+
request(options?: LissaOptionsInit): LissaRequest;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Upload a file
|
|
249
|
+
*/
|
|
250
|
+
upload(
|
|
251
|
+
file: File,
|
|
252
|
+
url?: string,
|
|
253
|
+
onProgress?: (uploaded: number, total: number) => void,
|
|
254
|
+
options?: Omit<LissaOptionsInit, 'url'>,
|
|
255
|
+
): LissaRequest;
|
|
256
|
+
upload(
|
|
257
|
+
file: File,
|
|
258
|
+
url?: string,
|
|
259
|
+
options?: Omit<LissaOptionsInit, 'url'>,
|
|
260
|
+
onProgress?: (uploaded: number, total: number) => void,
|
|
261
|
+
): LissaRequest;
|
|
262
|
+
upload(
|
|
263
|
+
file: File,
|
|
264
|
+
onProgress?: (uploaded: number, total: number) => void,
|
|
265
|
+
options?: LissaOptionsInit,
|
|
266
|
+
): LissaRequest;
|
|
267
|
+
upload(
|
|
268
|
+
file: File,
|
|
269
|
+
options?: LissaOptionsInit,
|
|
270
|
+
onProgress?: (uploaded: number, total: number) => void,
|
|
271
|
+
): LissaRequest;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Download a file
|
|
275
|
+
*/
|
|
276
|
+
download(
|
|
277
|
+
url?: string,
|
|
278
|
+
onProgress?: (downloaded: number, total: number) => void,
|
|
279
|
+
options?: Omit<LissaOptionsInit, 'url'>,
|
|
280
|
+
): LissaRequest;
|
|
281
|
+
download(
|
|
282
|
+
url?: string,
|
|
283
|
+
options?: Omit<LissaOptionsInit, 'url'>,
|
|
284
|
+
onProgress?: (downloaded: number, total: number) => void,
|
|
285
|
+
): LissaRequest;
|
|
286
|
+
download(
|
|
287
|
+
onProgress?: (downloaded: number, total: number) => void,
|
|
288
|
+
options?: LissaOptionsInit,
|
|
289
|
+
): LissaRequest;
|
|
290
|
+
download(
|
|
291
|
+
options?: LissaOptionsInit,
|
|
292
|
+
onProgress?: (downloaded: number, total: number) => void,
|
|
293
|
+
): LissaRequest;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
type Plugin = (lissa: Lissa) => void;
|
|
297
|
+
|
|
298
|
+
interface Lissa extends MakeRequest {
|
|
299
|
+
/**
|
|
300
|
+
* Modify the base options directly.
|
|
301
|
+
*
|
|
302
|
+
* Keep in mind that removing an option that is still in the defaults defined
|
|
303
|
+
* will get merged back into the final request.
|
|
304
|
+
*/
|
|
305
|
+
readonly options: DefaultOptions;
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Register a plugin
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* lissa.use(Lissa.retry());
|
|
312
|
+
*/
|
|
313
|
+
use(plugin: Plugin): Lissa;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Add a beforeRequest hook into the request cycle.
|
|
317
|
+
*
|
|
318
|
+
* Modify the given options as argument or return a new options object.
|
|
319
|
+
*/
|
|
320
|
+
beforeRequest(hook: (options: LissaOptions) => void | LissaOptions): Lissa;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Add a beforeFetch hook into the request cycle.
|
|
324
|
+
*
|
|
325
|
+
* Modify the actual fetch arguments or return new arguments.
|
|
326
|
+
*/
|
|
327
|
+
beforeFetch(hook: (request: FetchArguments) => void | FetchArguments): Lissa;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Add an onResponse hook into the request cycle.
|
|
331
|
+
*
|
|
332
|
+
* React to successful responses or modify them. A provided return value will
|
|
333
|
+
* stop looping over existing hooks and instantly returns this value (if it
|
|
334
|
+
* is an instance of Error it will get thrown).
|
|
335
|
+
*/
|
|
336
|
+
onResponse(hook: (result: LissaResult) => void | Exclude<any, undefined>): Lissa;
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Add an onError hook into the request cycle.
|
|
340
|
+
*
|
|
341
|
+
* React to errors or modify them. A provided return value will stop looping
|
|
342
|
+
* over existing hooks and instantly returns this value (if it is an instance
|
|
343
|
+
* of Error it will get thrown).
|
|
344
|
+
*/
|
|
345
|
+
onError(hook: (error: ResponseError | ConnectionError | GeneralErrorResponse) => void | Exclude<any, undefined>): Lissa;
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Copy the current instance with all its options and hooks.
|
|
349
|
+
*/
|
|
350
|
+
extend(options: DefaultOptionsInit): Lissa;
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Provide basic authentication credentials
|
|
354
|
+
*
|
|
355
|
+
* It sets the "Authorization" header to "Basic base64(username:password)"
|
|
356
|
+
*/
|
|
357
|
+
authenticate(username: string, password: string): Lissa;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
declare const LissaLib: MakeRequest & NamedExports & {
|
|
361
|
+
/**
|
|
362
|
+
* Perform a general fetch request.
|
|
363
|
+
*
|
|
364
|
+
* Specify method, body, headers and more in the given options object
|
|
365
|
+
*/
|
|
366
|
+
(
|
|
367
|
+
url: string,
|
|
368
|
+
options?: Omit<LissaOptionsInit, 'url'>
|
|
369
|
+
): LissaRequest;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Create a Lissa instance with the given base options.
|
|
373
|
+
*/
|
|
374
|
+
create(options: DefaultOptionsInit): Lissa;
|
|
375
|
+
create(
|
|
376
|
+
baseURL?: string,
|
|
377
|
+
options?: Omit<DefaultOptionsInit, 'baseURL'>
|
|
378
|
+
): Lissa;
|
|
379
|
+
};
|
|
380
|
+
|
|
381
|
+
export default LissaLib;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Global default options.
|
|
385
|
+
*/
|
|
386
|
+
export declare const defaults: DefaultOptions;
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* ConnectionError class for instance checking
|
|
391
|
+
*
|
|
392
|
+
* Will get thrown if a network-level error occurs (e.g. DNS resolution, connection lost)
|
|
393
|
+
*/
|
|
394
|
+
export declare class ConnectionError extends Error {
|
|
395
|
+
name: 'ConnectionError';
|
|
396
|
+
options: LissaOptions;
|
|
397
|
+
request: FetchArguments;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* ResponseError class for instance checking
|
|
402
|
+
*
|
|
403
|
+
* Will get thrown when the server responds with a status code that indicates a failure (non-2xx status)
|
|
404
|
+
*/
|
|
405
|
+
export declare class ResponseError extends Error {
|
|
406
|
+
name: 'ResponseError';
|
|
407
|
+
options: LissaOptions;
|
|
408
|
+
request: FetchArguments;
|
|
409
|
+
response: Response;
|
|
410
|
+
headers: Headers;
|
|
411
|
+
status: number;
|
|
412
|
+
data: null | string | Json | ReadableStream;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Retry plugin
|
|
417
|
+
*
|
|
418
|
+
* Retry requests on connection errors or server errors
|
|
419
|
+
*/
|
|
420
|
+
export declare const retry: (options: RetryOptions) => Plugin;
|
|
421
|
+
|
|
422
|
+
type CustomRetryError = {
|
|
423
|
+
/** custom retry type have to be returned by shouldRetry hook */
|
|
424
|
+
[K in `on${string}`]: number;
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
type RetryOptions = CustomRetryError & {
|
|
428
|
+
onConnectionError: number;
|
|
429
|
+
onGatewayError: number;
|
|
430
|
+
on429: number;
|
|
431
|
+
onServerError: number;
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Decide if the occurred error should trigger a retry.
|
|
435
|
+
*
|
|
436
|
+
* The given errorType helps preselecting error types. Return false to not
|
|
437
|
+
* trigger a retry. Return nothing if the given errorType is correct. Return
|
|
438
|
+
* a string to redefine the errorType or use a custom one. The number of
|
|
439
|
+
* maximum retries can be configured as `on${errorType}`. Return "CustomError"
|
|
440
|
+
* and define the retries as { onCustomError: 3 }
|
|
441
|
+
*/
|
|
442
|
+
shouldRetry(
|
|
443
|
+
errorType: void | 'ConnectionError' | 'GatewayError' | '429' | 'ServerError',
|
|
444
|
+
error: ResponseError | ConnectionError | GeneralErrorResponse,
|
|
445
|
+
): void | false | string;
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Hook into the retry logic after the retry is triggered and before the delay
|
|
449
|
+
* is awaited. Use beforeRetry e. g. if you want to change how long the delay
|
|
450
|
+
* should be or to notify a customer that the connection is lost.
|
|
451
|
+
*/
|
|
452
|
+
beforeRetry(
|
|
453
|
+
retry: { attempt: number, delay: number },
|
|
454
|
+
error: ResponseError | ConnectionError | GeneralErrorResponse,
|
|
455
|
+
): void | { attempt: number, delay: number };
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Hook into the retry logic after the delay is awaited and before the request
|
|
459
|
+
* gets resend. Use onRetry e. g. if you want to log that a retry is running now
|
|
460
|
+
*/
|
|
461
|
+
onRetry(
|
|
462
|
+
retry: { attempt: number, delay: number },
|
|
463
|
+
error: ResponseError | ConnectionError | GeneralErrorResponse,
|
|
464
|
+
): void;
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Hook into the retry logic after a request was successful. Use onSuccess
|
|
468
|
+
* e. g. if you want to dismiss a connection lost notification
|
|
469
|
+
*/
|
|
470
|
+
onSuccess(
|
|
471
|
+
retry: { attempt: number, delay: number },
|
|
472
|
+
res: ResultValue,
|
|
473
|
+
): void;
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Dedupe plugin
|
|
478
|
+
*
|
|
479
|
+
* Aborts leading or trailing requests to the same endpoint (depends on configured strategy [default is leading])
|
|
480
|
+
*/
|
|
481
|
+
export declare const dedupe: (options: DedupeOptions) => Plugin;
|
|
482
|
+
|
|
483
|
+
type DedupeOptions = {
|
|
484
|
+
/**
|
|
485
|
+
* Which request methods should be deduped. Defaults to "get"
|
|
486
|
+
*/
|
|
487
|
+
methods: HttpMethod[];
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* How to build the endpoint identifier. Defaults to url + method.
|
|
491
|
+
* Return false to skip dedupe logic.
|
|
492
|
+
*/
|
|
493
|
+
getIdentifier: (options: LissaOptions) => any;
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Define default strategy. Abort leading requests on new request or abort
|
|
497
|
+
* trailing new requests until first finishes. Can be also configured
|
|
498
|
+
* individually by adding a dedupe param to the request options.
|
|
499
|
+
*/
|
|
500
|
+
defaultStrategy: 'leading' | 'trailing';
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// Named exports are also params of the default export
|
|
504
|
+
interface NamedExports {
|
|
505
|
+
defaults: typeof defaults;
|
|
506
|
+
ConnectionError: typeof ConnectionError;
|
|
507
|
+
ResponseError: typeof ResponseError;
|
|
508
|
+
retry: typeof retry;
|
|
509
|
+
dedupe: typeof dedupe;
|
|
510
|
+
}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as exports from './exports.js';
|
|
2
|
+
import Lissa from './core/lissa.js';
|
|
3
|
+
|
|
4
|
+
const defaultLissa = Lissa.create();
|
|
5
|
+
|
|
6
|
+
const lib = (url, options = {}) => defaultLissa.request({
|
|
7
|
+
method: 'get',
|
|
8
|
+
...options,
|
|
9
|
+
url,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export default Object.assign(lib, {
|
|
13
|
+
'create': (...args) => Lissa.create(...args),
|
|
14
|
+
|
|
15
|
+
'get': (...args) => defaultLissa.get(...args),
|
|
16
|
+
'post': (...args) => defaultLissa.post(...args),
|
|
17
|
+
'put': (...args) => defaultLissa.put(...args),
|
|
18
|
+
'patch': (...args) => defaultLissa.patch(...args),
|
|
19
|
+
'delete': (...args) => defaultLissa.delete(...args),
|
|
20
|
+
'request': (...args) => defaultLissa.request(...args),
|
|
21
|
+
|
|
22
|
+
'upload': (...args) => defaultLissa.upload(...args),
|
|
23
|
+
'download': (...args) => defaultLissa.download(...args),
|
|
24
|
+
|
|
25
|
+
...exports,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export * from './exports.js';
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
const defaults = {
|
|
2
|
+
methods: ['get'],
|
|
3
|
+
getIdentifier: options => options.method + options.url,
|
|
4
|
+
defaultStrategy: 'leading', // or trailing
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export default (pluginOptions = {}) => (lissa) => {
|
|
8
|
+
pluginOptions = Object.assign({}, defaults, pluginOptions);
|
|
9
|
+
|
|
10
|
+
const requestMap = new Map();
|
|
11
|
+
|
|
12
|
+
lissa.beforeRequest(function beforeRequest(options) {
|
|
13
|
+
if (!options.dedupe && !pluginOptions.methods.includes(options.method)) return;
|
|
14
|
+
|
|
15
|
+
const dedupeStrategy = options.dedupe || pluginOptions.defaultStrategy;
|
|
16
|
+
if (dedupeStrategy === false) return;
|
|
17
|
+
|
|
18
|
+
const id = pluginOptions.getIdentifier(options);
|
|
19
|
+
if (!id) return;
|
|
20
|
+
|
|
21
|
+
let running = requestMap.get(id);
|
|
22
|
+
if (!running) requestMap.set(id, running = { value: 0, abortController: null });
|
|
23
|
+
|
|
24
|
+
running.value++;
|
|
25
|
+
this.on('settle', () => --running.value);
|
|
26
|
+
|
|
27
|
+
if (dedupeStrategy === 'trailing' && running.value > 1) {
|
|
28
|
+
options.signal = AbortSignal.abort();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
running.abortController?.abort();
|
|
33
|
+
running.abortController = new AbortController();
|
|
34
|
+
|
|
35
|
+
if (options.signal) {
|
|
36
|
+
options.signal = AbortSignal.any([
|
|
37
|
+
options.signal,
|
|
38
|
+
running.abortController.signal,
|
|
39
|
+
]);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
options.signal = running.abortController.signal;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
};
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const browserDefaults = {
|
|
2
|
+
onConnectionError: Infinity,
|
|
3
|
+
onGatewayError: Infinity,
|
|
4
|
+
on429: Infinity,
|
|
5
|
+
onServerError: 0,
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const nodeDefaults = {
|
|
9
|
+
onConnectionError: 3,
|
|
10
|
+
onGatewayError: 3,
|
|
11
|
+
on429: 3,
|
|
12
|
+
onServerError: 3,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Expecting to connect to own service in browser and vendor services in node
|
|
16
|
+
const defaults = typeof window === 'undefined' ? nodeDefaults : browserDefaults;
|
|
17
|
+
|
|
18
|
+
export default (options = {}) => (lissa) => {
|
|
19
|
+
options = Object.assign({}, defaults, options);
|
|
20
|
+
|
|
21
|
+
lissa.onError(async (error) => {
|
|
22
|
+
let errorType;
|
|
23
|
+
if (error.name === 'ConnectionError') errorType = 'ConnectionError';
|
|
24
|
+
else if (error.status === 429) errorType = '429';
|
|
25
|
+
else if (error.status === 500) errorType = 'ServerError';
|
|
26
|
+
else if (error.status >= 502 && error.status <= 504) errorType = 'GatewayError';
|
|
27
|
+
|
|
28
|
+
if (options.shouldRetry) {
|
|
29
|
+
const shouldRetry = await options.shouldRetry(errorType, error);
|
|
30
|
+
if (shouldRetry === false) return;
|
|
31
|
+
if (typeof shouldRetry === 'string') errorType = shouldRetry;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Return nothing to hand over the error to the next onError hook
|
|
35
|
+
if (!errorType) return;
|
|
36
|
+
|
|
37
|
+
// Initialize
|
|
38
|
+
let retry = error.options.retry || { attempt: 1, delay: 1000 };
|
|
39
|
+
|
|
40
|
+
if (options.beforeRetry) {
|
|
41
|
+
retry = await options.beforeRetry(retry, error) || retry;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Return nothing to hand over the error to the next onError hook
|
|
45
|
+
if (retry.attempt > (options[`on${errorType}`] || 0)) return;
|
|
46
|
+
|
|
47
|
+
await new Promise(resolve => setTimeout(resolve, retry.delay));
|
|
48
|
+
|
|
49
|
+
if (options.onRetry) {
|
|
50
|
+
await options.onRetry({ ...retry }, error);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
error.options.retry = {
|
|
54
|
+
attempt: retry.attempt + 1,
|
|
55
|
+
// await 1 sec on first retry, 2 sec on second retry ... (but max 5 sec)
|
|
56
|
+
delay: Math.min(1000 * (retry.attempt + 1), 5000),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const request = lissa.request(error.options);
|
|
60
|
+
|
|
61
|
+
if (options.onSuccess) request.then(res => options.onSuccess({ ...retry }, res));
|
|
62
|
+
|
|
63
|
+
return request;
|
|
64
|
+
});
|
|
65
|
+
};
|