soon-fetch 3.0.0 → 3.1.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/README.md +749 -23
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +33 -2
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,17 +36,20 @@
|
|
|
36
36
|
import { createSoon } from "soon-fetch";
|
|
37
37
|
|
|
38
38
|
const soon = createSoon(
|
|
39
|
-
(url, options) =>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
39
|
+
(url, options) => {
|
|
40
|
+
const isGet = !options?.method || options?.method.toLocaleLowerCase() === "get"
|
|
41
|
+
return {
|
|
42
|
+
baseURL: '/api',
|
|
43
|
+
baseOptions: {
|
|
44
|
+
timeout: 20 * 1000,
|
|
45
|
+
headers: new Headers({
|
|
46
|
+
Authorization: "Bearer " + localStorage.getItem("token"),
|
|
47
|
+
}),
|
|
48
|
+
share: isGet ? true : false,
|
|
49
|
+
staleTime: isGet ? 2 * 1000 : 0,
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
},
|
|
50
53
|
({ parsed }) => {
|
|
51
54
|
return <T>() => {
|
|
52
55
|
return fetch(parsed.url, parsed.options).then((res) =>
|
|
@@ -204,13 +207,373 @@ type SoonOptions = Omit<RequestInit, "body"> & {
|
|
|
204
207
|
};
|
|
205
208
|
```
|
|
206
209
|
|
|
210
|
+
#### createSoon
|
|
211
|
+
|
|
212
|
+
Create a soon request instance.
|
|
213
|
+
|
|
214
|
+
**Parameters:**
|
|
215
|
+
|
|
216
|
+
- `getConfig`: A function to get request configuration, receives url and options, returns an object containing url, options, baseURL, and baseOptions
|
|
217
|
+
- `wrapper`: A wrapper function to handle request logic
|
|
218
|
+
|
|
219
|
+
**Returns:** An object containing request method and various shortcut methods
|
|
220
|
+
|
|
221
|
+
**Example:**
|
|
222
|
+
|
|
223
|
+
```javascript
|
|
224
|
+
const soon = createSoon(
|
|
225
|
+
(url, options) => ({ url, options,baseURL:'/api', baseOptions:{timeout: 5000} }),
|
|
226
|
+
({ parsed }) =>
|
|
227
|
+
async (url, options) => {
|
|
228
|
+
const response = await fetch(parsed.url, parsed.options);
|
|
229
|
+
return response.json();
|
|
230
|
+
}
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
// Usage example
|
|
234
|
+
const data = await soon.get("/api/users");
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### createShortApi
|
|
238
|
+
|
|
239
|
+
Factory function to create API shortcut methods.
|
|
240
|
+
Used to generate type-safe API calling methods, supporting path parameters, query parameters, and request body.
|
|
241
|
+
|
|
242
|
+
**Parameters:**
|
|
243
|
+
|
|
244
|
+
- `wrapper`: Wrapper function to handle actual request logic
|
|
245
|
+
|
|
246
|
+
**Returns:** An object containing GET, POST, PUT, DELETE, PATCH and other methods, each method supports chain calling
|
|
247
|
+
|
|
248
|
+
**Example:**
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
const API = createShortApi(
|
|
252
|
+
async (url, method, params, query, body, options) => {
|
|
253
|
+
// Handle request logic
|
|
254
|
+
const _url = mergeUrl(url, { params, query });
|
|
255
|
+
const response = await fetch(_url, { ...options, method, body });
|
|
256
|
+
return response.json();
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
// Usage example
|
|
261
|
+
const getUser = API.GET("/api/users/:id").Send();
|
|
262
|
+
const userData = await getUser({ id: 1 });
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### createShortMethods
|
|
266
|
+
|
|
267
|
+
Factory function to create shortcut methods.
|
|
268
|
+
|
|
269
|
+
**Parameters:**
|
|
270
|
+
|
|
271
|
+
- `methods`: HTTP methods array
|
|
272
|
+
- `wrapper`: Wrapper function that receives method name and returns a function to process requests
|
|
273
|
+
|
|
274
|
+
**Returns:** Shortcut call object containing specified methods
|
|
275
|
+
|
|
276
|
+
**Example:**
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
const methods = createShortMethods(["get", "post"], (method) => {
|
|
280
|
+
return (url, options) => fetch(url, { ...options, method });
|
|
281
|
+
});
|
|
282
|
+
// Usage: methods.get('/api/users')
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### parseUrlOptions
|
|
286
|
+
|
|
287
|
+
Parse URL options.
|
|
288
|
+
|
|
289
|
+
**Parameters:**
|
|
290
|
+
|
|
291
|
+
- `urlOptions`: Object containing url, options, baseURL and baseOptions
|
|
292
|
+
|
|
293
|
+
**Returns:** Tuple of processed url and options
|
|
294
|
+
|
|
295
|
+
**Example:**
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
const [url, options] = parseUrlOptions({
|
|
299
|
+
url: "/api/users/:id",
|
|
300
|
+
options: { params: { id: "123" } },
|
|
301
|
+
baseURL: "https://api.example.com",
|
|
302
|
+
});
|
|
303
|
+
// Returns: ['https://api.example.com/api/users/123', options]
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
#### mergeHeaders
|
|
307
|
+
|
|
308
|
+
Merge multiple Headers objects.
|
|
309
|
+
|
|
310
|
+
**Parameters:**
|
|
311
|
+
|
|
312
|
+
- `headersList`: List of Headers objects to merge
|
|
313
|
+
|
|
314
|
+
**Returns:** Merged Headers object, later ones will overwrite earlier ones with the same name
|
|
315
|
+
|
|
316
|
+
**Example:**
|
|
317
|
+
|
|
318
|
+
```javascript
|
|
319
|
+
const headers1 = { "Content-Type": "application/json" };
|
|
320
|
+
const headers2 = { Authorization: "Bearer token" };
|
|
321
|
+
const mergedHeaders = mergeHeaders(headers1, headers2);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
#### mergeSignals
|
|
325
|
+
|
|
326
|
+
Merge multiple AbortSignal signals.
|
|
327
|
+
|
|
328
|
+
**Parameters:**
|
|
329
|
+
|
|
330
|
+
- `signals`: Array of AbortSignals to merge
|
|
331
|
+
- `timeout`: Optional timeout time (milliseconds)
|
|
332
|
+
|
|
333
|
+
**Returns:** Merged AbortSignal, any signal termination will trigger termination
|
|
334
|
+
|
|
335
|
+
**Example:**
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
const controller1 = new AbortController();
|
|
339
|
+
const controller2 = new AbortController();
|
|
340
|
+
const mergedSignal = mergeSignals(
|
|
341
|
+
[controller1.signal, controller2.signal],
|
|
342
|
+
5000
|
|
343
|
+
);
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
#### mergeUrl
|
|
347
|
+
|
|
348
|
+
Merge URL and its related parameters.
|
|
349
|
+
Handle baseURL, path parameters and query parameters to generate complete URL.
|
|
350
|
+
|
|
351
|
+
**Parameters:**
|
|
352
|
+
|
|
353
|
+
- `url`: Original URL
|
|
354
|
+
- `config`: Configuration object, including query parameters, path parameters and base URL
|
|
355
|
+
|
|
356
|
+
**Returns:** Processed complete URL
|
|
357
|
+
|
|
358
|
+
**Example:**
|
|
359
|
+
|
|
360
|
+
```javascript
|
|
361
|
+
const url = mergeUrl("/api/users/:id", {
|
|
362
|
+
params: { id: "123" },
|
|
363
|
+
query: { filter: "active" },
|
|
364
|
+
baseURL: "https://api.example.com",
|
|
365
|
+
});
|
|
366
|
+
// Returns: 'https://api.example.com/api/users/123?filter=active'
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
#### mergeOptions
|
|
370
|
+
|
|
371
|
+
Merge multiple option objects.
|
|
372
|
+
Merge request options, including special handling of headers and signals.
|
|
373
|
+
|
|
374
|
+
**Parameters:**
|
|
375
|
+
|
|
376
|
+
- `optionsList`: List of option objects to merge
|
|
377
|
+
|
|
378
|
+
**Returns:** Merged option object
|
|
379
|
+
|
|
380
|
+
**Example:**
|
|
381
|
+
|
|
382
|
+
```javascript
|
|
383
|
+
const defaultOptions = {
|
|
384
|
+
timeout: 5000,
|
|
385
|
+
headers: { "Content-Type": "application/json" },
|
|
386
|
+
};
|
|
387
|
+
const requestOptions = {
|
|
388
|
+
method: "POST",
|
|
389
|
+
body: JSON.stringify({ name: "John" }),
|
|
390
|
+
};
|
|
391
|
+
const mergedOptions = mergeOptions(defaultOptions, requestOptions);
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
#### isBodyJson
|
|
395
|
+
|
|
396
|
+
Determine if the request body is a JSON object.
|
|
397
|
+
Check if body is a plain object, not special types like FormData or Blob.
|
|
398
|
+
|
|
399
|
+
**Parameters:**
|
|
400
|
+
|
|
401
|
+
- `body`: Request body
|
|
402
|
+
|
|
403
|
+
**Returns:** Returns true if it is a JSON object, otherwise false
|
|
404
|
+
|
|
405
|
+
**Example:**
|
|
406
|
+
|
|
407
|
+
```javascript
|
|
408
|
+
isBodyJson({ name: "John" }); // true
|
|
409
|
+
isBodyJson(new FormData()); // false
|
|
410
|
+
isBodyJson("string"); // false
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
#### genRequestKey
|
|
414
|
+
|
|
415
|
+
Generate a unique identification key for the request.
|
|
416
|
+
Generate a unique key value based on the request's URL, method, headers, body, query parameters, etc., used for caching and request sharing.
|
|
417
|
+
|
|
418
|
+
**Parameters:**
|
|
419
|
+
|
|
420
|
+
- `req`: Request object containing url and options
|
|
421
|
+
|
|
422
|
+
**Returns:** Unique identification string of the request
|
|
423
|
+
|
|
424
|
+
**Example:**
|
|
425
|
+
|
|
426
|
+
```javascript
|
|
427
|
+
const key = genRequestKey({
|
|
428
|
+
url: "/api/users",
|
|
429
|
+
options: { method: "GET", params: { id: 1 } },
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
#### raceAbort
|
|
434
|
+
|
|
435
|
+
Race condition handling function.
|
|
436
|
+
Used to handle request race conditions, terminating previous requests.
|
|
437
|
+
|
|
438
|
+
**Parameters:**
|
|
439
|
+
|
|
440
|
+
- `abortController`: Controller of the current request
|
|
441
|
+
- `controllers`: Array of existing controllers
|
|
442
|
+
|
|
443
|
+
**Example:**
|
|
444
|
+
|
|
445
|
+
```javascript
|
|
446
|
+
const controller = new AbortController();
|
|
447
|
+
const controllers = [];
|
|
448
|
+
raceAbort(controller, controllers); // Terminate previous requests and add current controller
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
#### createCache
|
|
452
|
+
|
|
453
|
+
Create cache instance.
|
|
454
|
+
Provides request result caching function, supports expiration time.
|
|
455
|
+
|
|
456
|
+
**Returns:** Cache object containing get, set, remove methods
|
|
457
|
+
|
|
458
|
+
**Example:**
|
|
459
|
+
|
|
460
|
+
```javascript
|
|
461
|
+
const cache = createCache();
|
|
462
|
+
cache.set("key", { data: "value" }, Date.now() + 60000); // Cache for 1 minute
|
|
463
|
+
const data = cache.get("key");
|
|
464
|
+
cache.remove("key");
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
#### deepSort
|
|
468
|
+
|
|
469
|
+
Deep sort object keys.
|
|
470
|
+
Recursively sort the keys of an object to generate a stable object serialization result.
|
|
471
|
+
|
|
472
|
+
**Parameters:**
|
|
473
|
+
|
|
474
|
+
- `obj`: Object to sort
|
|
475
|
+
|
|
476
|
+
**Returns:** Object with sorted keys
|
|
477
|
+
|
|
478
|
+
**Example:**
|
|
479
|
+
|
|
480
|
+
```javascript
|
|
481
|
+
const obj = { b: 2, a: 1, c: { z: 3, y: 2 } };
|
|
482
|
+
const sorted = deepSort(obj);
|
|
483
|
+
// Returns: { a: 1, b: 2, c: { y: 2, z: 3 } }
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
#### createShare
|
|
487
|
+
|
|
488
|
+
Create request sharing instance.
|
|
489
|
+
Used to share the same request to avoid duplicate requests.
|
|
490
|
+
|
|
491
|
+
**Returns:** Sharing object containing get and set methods
|
|
492
|
+
|
|
493
|
+
**Example:**
|
|
494
|
+
|
|
495
|
+
```javascript
|
|
496
|
+
const share = createShare();
|
|
497
|
+
const promise = fetch("/api/data");
|
|
498
|
+
share.set("key", promise);
|
|
499
|
+
const sharedPromise = share.get("key"); // Get the same promise
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
#### createSilentRefresh
|
|
503
|
+
|
|
504
|
+
Create silent refresh instance.
|
|
505
|
+
Used to handle silent refresh functionality when token expires.
|
|
506
|
+
|
|
507
|
+
**Parameters:**
|
|
508
|
+
|
|
509
|
+
- `refresh_token_fn`: Function to refresh token
|
|
510
|
+
|
|
511
|
+
**Returns:** Function that accepts success and failure callbacks
|
|
512
|
+
|
|
513
|
+
**Example:**
|
|
514
|
+
|
|
515
|
+
```javascript
|
|
516
|
+
const silentRefresh = createSilentRefresh(async () => {
|
|
517
|
+
// Refresh token logic
|
|
518
|
+
await refreshToken();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// Usage example
|
|
522
|
+
silentRefresh(
|
|
523
|
+
() => console.log("Refresh successful"),
|
|
524
|
+
() => console.log("Refresh failed")
|
|
525
|
+
);
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
#### parseWithBase
|
|
529
|
+
|
|
530
|
+
Parse base URL configuration.
|
|
531
|
+
Process baseURL, headers, body and other configuration items to generate final request configuration.
|
|
532
|
+
|
|
533
|
+
**Parameters:**
|
|
534
|
+
|
|
535
|
+
- `urlOptions`: Object containing url, options, baseURL and baseOptions
|
|
536
|
+
|
|
537
|
+
**Returns:** Processed url, options, is_body_json and abortController
|
|
538
|
+
|
|
539
|
+
**Example:**
|
|
540
|
+
|
|
541
|
+
```javascript
|
|
542
|
+
const result = parseWithBase({
|
|
543
|
+
url: "/api/users",
|
|
544
|
+
options: { method: "GET" },
|
|
545
|
+
baseURL: "https://api.example.com",
|
|
546
|
+
});
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
#### toFormData
|
|
550
|
+
|
|
551
|
+
Convert object to FormData.
|
|
552
|
+
Used to convert plain objects to FormData format, supports files and regular values.
|
|
553
|
+
|
|
554
|
+
**Parameters:**
|
|
555
|
+
|
|
556
|
+
- `body`: Object to convert
|
|
557
|
+
|
|
558
|
+
**Returns:** Converted FormData object
|
|
559
|
+
|
|
560
|
+
**Example:**
|
|
561
|
+
|
|
562
|
+
```javascript
|
|
563
|
+
const formData = toFormData({
|
|
564
|
+
name: "John",
|
|
565
|
+
avatar: fileBlob,
|
|
566
|
+
age: 30,
|
|
567
|
+
});
|
|
568
|
+
```
|
|
569
|
+
|
|
207
570
|
[English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
|
|
208
571
|
|
|
209
572
|
<!-- omit in toc -->
|
|
210
573
|
|
|
211
574
|
#### soon-fetch
|
|
212
575
|
|
|
213
|
-
**极轻量的请求库,不到
|
|
576
|
+
**极轻量的请求库,不到 5K**
|
|
214
577
|
|
|
215
578
|
> - 🌐 自动解析 rest Url 的参数
|
|
216
579
|
> - ⭐ 快捷定义请求 api
|
|
@@ -243,17 +606,20 @@ type SoonOptions = Omit<RequestInit, "body"> & {
|
|
|
243
606
|
|
|
244
607
|
```typescript
|
|
245
608
|
const soon = createSoon(
|
|
246
|
-
(url, options) =>
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
609
|
+
(url, options) => {
|
|
610
|
+
const isGet = !options?.method || options?.method.toLocaleLowerCase() === "get"
|
|
611
|
+
return {
|
|
612
|
+
baseURL: '/api',
|
|
613
|
+
baseOptions: {
|
|
614
|
+
timeout: 20 * 1000,
|
|
615
|
+
headers: new Headers({
|
|
616
|
+
Authorization: "Bearer " + localStorage.getItem("token"),
|
|
617
|
+
}),
|
|
618
|
+
share: isGet ? true : false,
|
|
619
|
+
staleTime: isGet ? 2 * 1000 : 0,
|
|
620
|
+
},
|
|
621
|
+
}
|
|
622
|
+
},
|
|
257
623
|
({ parsed }) => {
|
|
258
624
|
return <T>() => {
|
|
259
625
|
return fetch(parsed.url, parsed.options).then((res) =>
|
|
@@ -414,6 +780,366 @@ type SoonOptions = Omit<RequestInit, "body"> & {
|
|
|
414
780
|
};
|
|
415
781
|
```
|
|
416
782
|
|
|
783
|
+
#### createSoon
|
|
784
|
+
|
|
785
|
+
创建一个 soon 请求实例。
|
|
786
|
+
|
|
787
|
+
**参数:**
|
|
788
|
+
|
|
789
|
+
- `getConfig`: 用于获取请求配置的函数,接收 url 和 options,返回包含 url、options、baseURL 和 baseOptions 的对象
|
|
790
|
+
- `wrapper`: 包装器函数,用于处理请求逻辑
|
|
791
|
+
|
|
792
|
+
**返回:** 包含 request 方法和各种快捷方法的对象
|
|
793
|
+
|
|
794
|
+
**示例:**
|
|
795
|
+
|
|
796
|
+
```javascript
|
|
797
|
+
const soon = createSoon(
|
|
798
|
+
(url, options) => ({ url, options,baseURL:'/api', baseOptions:{timeout: 5000} }),
|
|
799
|
+
({ parsed }) =>
|
|
800
|
+
async (url, options) => {
|
|
801
|
+
const response = await fetch(parsed.url, parsed.options);
|
|
802
|
+
return response.json();
|
|
803
|
+
}
|
|
804
|
+
);
|
|
805
|
+
// 使用示例
|
|
806
|
+
const data = await soon.get("/api/users");
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
#### createShortApi
|
|
810
|
+
|
|
811
|
+
创建 API 快捷方法的工厂函数。
|
|
812
|
+
用于生成类型安全的 API 调用方法,支持路径参数、查询参数和请求体。
|
|
813
|
+
|
|
814
|
+
**参数:**
|
|
815
|
+
|
|
816
|
+
- `wrapper`: 包装函数,用于处理实际的请求逻辑
|
|
817
|
+
|
|
818
|
+
**返回:** 包含 GET、POST、PUT、DELETE、PATCH 等方法的对象,每个方法都支持链式调用
|
|
819
|
+
|
|
820
|
+
**示例:**
|
|
821
|
+
|
|
822
|
+
```javascript
|
|
823
|
+
const API = createShortApi(
|
|
824
|
+
async (url, method, params, query, body, options) => {
|
|
825
|
+
// 处理请求逻辑
|
|
826
|
+
const _url = mergeUrl(url, { params, query });
|
|
827
|
+
const response = await fetch(_url, { ...options, method, body });
|
|
828
|
+
return response.json();
|
|
829
|
+
}
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
// 使用示例
|
|
833
|
+
const getUser = API.GET("/api/users/:id").Send();
|
|
834
|
+
const userData = await getUser({ id: 1 });
|
|
835
|
+
```
|
|
836
|
+
|
|
837
|
+
#### createShortMethods
|
|
838
|
+
|
|
839
|
+
创建快捷方法的工厂函数。
|
|
840
|
+
|
|
841
|
+
**参数:**
|
|
842
|
+
|
|
843
|
+
- `methods`: HTTP 方法数组
|
|
844
|
+
- `wrapper`: 包装函数,接收方法名,返回处理请求的函数
|
|
845
|
+
|
|
846
|
+
**返回:** 包含指定方法的快捷调用对象
|
|
847
|
+
|
|
848
|
+
**示例:**
|
|
849
|
+
|
|
850
|
+
```javascript
|
|
851
|
+
const methods = createShortMethods(["get", "post"], (method) => {
|
|
852
|
+
return (url, options) => fetch(url, { ...options, method });
|
|
853
|
+
});
|
|
854
|
+
// 使用: methods.get('/api/users')
|
|
855
|
+
```
|
|
856
|
+
|
|
857
|
+
#### parseUrlOptions
|
|
858
|
+
|
|
859
|
+
解析 URL 选项。
|
|
860
|
+
|
|
861
|
+
**参数:**
|
|
862
|
+
|
|
863
|
+
- `urlOptions`: 包含 url、options、baseURL 和 baseOptions 的对象
|
|
864
|
+
|
|
865
|
+
**返回:** 处理后的 url 和 options 元组
|
|
866
|
+
|
|
867
|
+
**示例:**
|
|
868
|
+
|
|
869
|
+
```javascript
|
|
870
|
+
const [url, options] = parseUrlOptions({
|
|
871
|
+
url: "/api/users/:id",
|
|
872
|
+
options: { params: { id: "123" } },
|
|
873
|
+
baseURL: "https://api.example.com",
|
|
874
|
+
});
|
|
875
|
+
// 返回: ['https://api.example.com/api/users/123', options]
|
|
876
|
+
```
|
|
877
|
+
|
|
878
|
+
#### mergeHeaders
|
|
879
|
+
|
|
880
|
+
合并多个 Headers 对象。
|
|
881
|
+
|
|
882
|
+
**参数:**
|
|
883
|
+
|
|
884
|
+
- `headersList`: 要合并的 Headers 对象列表
|
|
885
|
+
|
|
886
|
+
**返回:** 合并后的 Headers 对象,后面的会覆盖前面的同名 header
|
|
887
|
+
|
|
888
|
+
**示例:**
|
|
889
|
+
|
|
890
|
+
```javascript
|
|
891
|
+
const headers1 = { "Content-Type": "application/json" };
|
|
892
|
+
const headers2 = { Authorization: "Bearer token" };
|
|
893
|
+
const mergedHeaders = mergeHeaders(headers1, headers2);
|
|
894
|
+
```
|
|
895
|
+
|
|
896
|
+
#### mergeSignals
|
|
897
|
+
|
|
898
|
+
合并多个 AbortSignal 信号。
|
|
899
|
+
|
|
900
|
+
**参数:**
|
|
901
|
+
|
|
902
|
+
- `signals`: 要合并的 AbortSignal 数组
|
|
903
|
+
- `timeout`: 可选的超时时间(毫秒)
|
|
904
|
+
|
|
905
|
+
**返回:** 合并后的 AbortSignal,任意一个信号终止都会触发终止
|
|
906
|
+
|
|
907
|
+
**示例:**
|
|
908
|
+
|
|
909
|
+
```javascript
|
|
910
|
+
const controller1 = new AbortController();
|
|
911
|
+
const controller2 = new AbortController();
|
|
912
|
+
const mergedSignal = mergeSignals(
|
|
913
|
+
[controller1.signal, controller2.signal],
|
|
914
|
+
5000
|
|
915
|
+
);
|
|
916
|
+
```
|
|
917
|
+
|
|
918
|
+
#### mergeUrl
|
|
919
|
+
|
|
920
|
+
合并 URL 及其相关参数。
|
|
921
|
+
处理 baseURL、路径参数和查询参数,生成完整 URL。
|
|
922
|
+
|
|
923
|
+
**参数:**
|
|
924
|
+
|
|
925
|
+
- `url`: 原始 URL
|
|
926
|
+
- `config`: 配置对象,包含查询参数、路径参数和基础 URL
|
|
927
|
+
|
|
928
|
+
**返回:** 处理后的完整 URL
|
|
929
|
+
|
|
930
|
+
**示例:**
|
|
931
|
+
|
|
932
|
+
```javascript
|
|
933
|
+
const url = mergeUrl("/api/users/:id", {
|
|
934
|
+
params: { id: "123" },
|
|
935
|
+
query: { filter: "active" },
|
|
936
|
+
baseURL: "https://api.example.com",
|
|
937
|
+
});
|
|
938
|
+
// 返回: 'https://api.example.com/api/users/123?filter=active'
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
#### mergeOptions
|
|
942
|
+
|
|
943
|
+
合并多个选项对象。
|
|
944
|
+
合并请求选项,包括 headers 和 signal 等特殊处理。
|
|
945
|
+
|
|
946
|
+
**参数:**
|
|
947
|
+
|
|
948
|
+
- `optionsList`: 要合并的选项对象列表
|
|
949
|
+
|
|
950
|
+
**返回:** 合并后的选项对象
|
|
951
|
+
|
|
952
|
+
**示例:**
|
|
953
|
+
|
|
954
|
+
```javascript
|
|
955
|
+
const defaultOptions = {
|
|
956
|
+
timeout: 5000,
|
|
957
|
+
headers: { "Content-Type": "application/json" },
|
|
958
|
+
};
|
|
959
|
+
const requestOptions = {
|
|
960
|
+
method: "POST",
|
|
961
|
+
body: JSON.stringify({ name: "John" }),
|
|
962
|
+
};
|
|
963
|
+
const mergedOptions = mergeOptions(defaultOptions, requestOptions);
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
#### isBodyJson
|
|
967
|
+
|
|
968
|
+
判断请求体是否为 JSON 对象。
|
|
969
|
+
检查 body 是否为普通对象,而不是 FormData、Blob 等特殊类型。
|
|
970
|
+
|
|
971
|
+
**参数:**
|
|
972
|
+
|
|
973
|
+
- `body`: 请求体
|
|
974
|
+
|
|
975
|
+
**返回:** 如果是 JSON 对象返回 true,否则返回 false
|
|
976
|
+
|
|
977
|
+
**示例:**
|
|
978
|
+
|
|
979
|
+
```javascript
|
|
980
|
+
isBodyJson({ name: "John" }); // true
|
|
981
|
+
isBodyJson(new FormData()); // false
|
|
982
|
+
isBodyJson("string"); // false
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
#### genRequestKey
|
|
986
|
+
|
|
987
|
+
生成请求的唯一标识键。
|
|
988
|
+
根据请求的 URL、方法、headers、body、查询参数等生成唯一键值,用于缓存和请求共享。
|
|
989
|
+
|
|
990
|
+
**参数:**
|
|
991
|
+
|
|
992
|
+
- `req`: 包含 url 和 options 的请求对象
|
|
993
|
+
|
|
994
|
+
**返回:** 请求的唯一标识字符串
|
|
995
|
+
|
|
996
|
+
**示例:**
|
|
997
|
+
|
|
998
|
+
```javascript
|
|
999
|
+
const key = genRequestKey({
|
|
1000
|
+
url: "/api/users",
|
|
1001
|
+
options: { method: "GET", params: { id: 1 } },
|
|
1002
|
+
});
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
#### raceAbort
|
|
1006
|
+
|
|
1007
|
+
竞态处理函数。
|
|
1008
|
+
用于处理请求竞态,终止之前的请求。
|
|
1009
|
+
|
|
1010
|
+
**参数:**
|
|
1011
|
+
|
|
1012
|
+
- `abortController`: 当前请求的控制器
|
|
1013
|
+
- `controllers`: 已存在的控制器数组
|
|
1014
|
+
|
|
1015
|
+
**示例:**
|
|
1016
|
+
|
|
1017
|
+
```javascript
|
|
1018
|
+
const controller = new AbortController();
|
|
1019
|
+
const controllers = [];
|
|
1020
|
+
raceAbort(controller, controllers); // 终止之前的请求并添加当前控制器
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
#### createCache
|
|
1024
|
+
|
|
1025
|
+
创建缓存实例。
|
|
1026
|
+
提供请求结果缓存功能,支持过期时间。
|
|
1027
|
+
|
|
1028
|
+
**返回:** 包含 get、set、remove 方法的缓存对象
|
|
1029
|
+
|
|
1030
|
+
**示例:**
|
|
1031
|
+
|
|
1032
|
+
```javascript
|
|
1033
|
+
const cache = createCache();
|
|
1034
|
+
cache.set("key", { data: "value" }, Date.now() + 60000); // 缓存1分钟
|
|
1035
|
+
const data = cache.get("key");
|
|
1036
|
+
cache.remove("key");
|
|
1037
|
+
```
|
|
1038
|
+
|
|
1039
|
+
#### deepSort
|
|
1040
|
+
|
|
1041
|
+
深度排序对象键。
|
|
1042
|
+
递归地对对象的键进行排序,用于生成稳定的对象序列化结果。
|
|
1043
|
+
|
|
1044
|
+
**参数:**
|
|
1045
|
+
|
|
1046
|
+
- `obj`: 要排序的对象
|
|
1047
|
+
|
|
1048
|
+
**返回:** 键排序后的对象
|
|
1049
|
+
|
|
1050
|
+
**示例:**
|
|
1051
|
+
|
|
1052
|
+
```javascript
|
|
1053
|
+
const obj = { b: 2, a: 1, c: { z: 3, y: 2 } };
|
|
1054
|
+
const sorted = deepSort(obj);
|
|
1055
|
+
// 返回: { a: 1, b: 2, c: { y: 2, z: 3 } }
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
#### createShare
|
|
1059
|
+
|
|
1060
|
+
创建请求共享实例。
|
|
1061
|
+
用于共享相同请求,避免重复请求。
|
|
1062
|
+
|
|
1063
|
+
**返回:** 包含 get 和 set 方法的共享对象
|
|
1064
|
+
|
|
1065
|
+
**示例:**
|
|
1066
|
+
|
|
1067
|
+
```javascript
|
|
1068
|
+
const share = createShare();
|
|
1069
|
+
const promise = fetch("/api/data");
|
|
1070
|
+
share.set("key", promise);
|
|
1071
|
+
const sharedPromise = share.get("key"); // 获取相同 promise
|
|
1072
|
+
```
|
|
1073
|
+
|
|
1074
|
+
#### createSilentRefresh
|
|
1075
|
+
|
|
1076
|
+
创建静默刷新实例。
|
|
1077
|
+
用于处理 token 过期时的静默刷新功能。
|
|
1078
|
+
|
|
1079
|
+
**参数:**
|
|
1080
|
+
|
|
1081
|
+
- `refresh_token_fn`: 刷新 token 的函数
|
|
1082
|
+
|
|
1083
|
+
**返回:** 接收成功和失败回调的函数
|
|
1084
|
+
|
|
1085
|
+
**示例:**
|
|
1086
|
+
|
|
1087
|
+
```javascript
|
|
1088
|
+
const silentRefresh = createSilentRefresh(async () => {
|
|
1089
|
+
// 刷新token逻辑
|
|
1090
|
+
await refreshToken();
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
// 使用示例
|
|
1094
|
+
silentRefresh(
|
|
1095
|
+
() => console.log("刷新成功"),
|
|
1096
|
+
() => console.log("刷新失败")
|
|
1097
|
+
);
|
|
1098
|
+
```
|
|
1099
|
+
|
|
1100
|
+
#### parseWithBase
|
|
1101
|
+
|
|
1102
|
+
解析基础 URL 配置。
|
|
1103
|
+
处理 baseURL、headers、body 等配置项,生成最终的请求配置。
|
|
1104
|
+
|
|
1105
|
+
**参数:**
|
|
1106
|
+
|
|
1107
|
+
- `urlOptions`: 包含 url、options、baseURL 和 baseOptions 的对象
|
|
1108
|
+
|
|
1109
|
+
**返回:** 处理后的 url、options、is_body_json 和 abortController
|
|
1110
|
+
|
|
1111
|
+
**示例:**
|
|
1112
|
+
|
|
1113
|
+
```javascript
|
|
1114
|
+
const result = parseWithBase({
|
|
1115
|
+
url: "/api/users",
|
|
1116
|
+
options: { method: "GET" },
|
|
1117
|
+
baseURL: "https://api.example.com",
|
|
1118
|
+
});
|
|
1119
|
+
```
|
|
1120
|
+
|
|
1121
|
+
#### toFormData
|
|
1122
|
+
|
|
1123
|
+
将对象转换为 FormData。
|
|
1124
|
+
用于将普通对象转换为 FormData 格式,支持文件和普通值。
|
|
1125
|
+
|
|
1126
|
+
**参数:**
|
|
1127
|
+
|
|
1128
|
+
- `body`: 要转换的对象
|
|
1129
|
+
|
|
1130
|
+
**返回:** 转换后的 FormData 对象
|
|
1131
|
+
|
|
1132
|
+
**示例:**
|
|
1133
|
+
|
|
1134
|
+
```javascript
|
|
1135
|
+
const formData = toFormData({
|
|
1136
|
+
name: "John",
|
|
1137
|
+
avatar: fileBlob,
|
|
1138
|
+
age: 30,
|
|
1139
|
+
});
|
|
1140
|
+
```
|
|
1141
|
+
|
|
1142
|
+
|
|
417
1143
|
[English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
|
|
418
1144
|
|
|
419
1145
|
<!-- omit in toc -->
|
package/dist/index.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";const e=e=>{const t=[],r=e.match(/:([^:/\d]+)\/?/g);return r&&r.forEach((e=>{t.push(e.replace(/\//g,"").replace(/:/g,""))})),t},t=(e="")=>e.endsWith("/")?e.slice(0,-1):e,r=(e="")=>e.startsWith("/")?e:"/"+e,n=(e="")=>e.startsWith("http"),o=e=>{if(!e)return[];if(e instanceof URLSearchParams||"string"==typeof e||Array.isArray(e))return Array.from(new URLSearchParams(e).entries());const t=[];return Object.keys(e).forEach((r=>{const n=e[r];(Array.isArray(n)?n:[n]).forEach((e=>{t.push([r,e??""])}))})),t},s=(s,a)=>{const{query:i,params:c,baseURL:f}=a;let p=s.trim();e(s).forEach((e=>{c&&(p=p.replace(":"+e,""+c[e]))}));const[u,l]=p.split("?"),h=new URLSearchParams([...o(l),...o(i)]);let y=((e,o)=>{if(n(e))return e;const s=n(o)?o:r(o);return t(s)+t(r(e))})(u,f);return h.size&&(y=y+"?"+h),y},a=(...e)=>{const t=new Headers;return e.forEach((e=>{e&&new Headers(e).forEach(((e,r)=>{t.set(r,e)}))})),t};function i(e,t){const r=(e??[]).filter((e=>!!e));return t&&r.push(AbortSignal.timeout(t)),r.length?AbortSignal.any(r):void 0}function c(e){return!(!e||"object"!=typeof e||(e instanceof Blob||e instanceof ArrayBuffer||e instanceof FormData||e instanceof File||e instanceof DataView||e instanceof URLSearchParams||e instanceof ReadableStream||(t=e,t instanceof Int8Array||t instanceof Uint8Array||t instanceof Uint8ClampedArray||t instanceof Int16Array||t instanceof Uint16Array||t instanceof Int32Array||t instanceof Uint32Array||t instanceof Float32Array||t instanceof Float64Array||t instanceof BigInt64Array||t instanceof BigUint64Array)));var t}function f(...e){const t=Object.assign({},...e),r=a(...e.map((e=>e?.headers)));return t.headers=r,t.signal=i(e.map((e=>e?.signal)),t.timeout),t}function
|
|
1
|
+
"use strict";const e=e=>{const t=[],r=e.match(/:([^:/\d]+)\/?/g);return r&&r.forEach((e=>{t.push(e.replace(/\//g,"").replace(/:/g,""))})),t},t=(e="")=>e.endsWith("/")?e.slice(0,-1):e,r=(e="")=>e.startsWith("/")?e:"/"+e,n=(e="")=>e.startsWith("http"),o=e=>{if(!e)return[];if(e instanceof URLSearchParams||"string"==typeof e||Array.isArray(e))return Array.from(new URLSearchParams(e).entries());const t=[];return Object.keys(e).forEach((r=>{const n=e[r];(Array.isArray(n)?n:[n]).forEach((e=>{t.push([r,e??""])}))})),t},s=(s,a)=>{const{query:i,params:c,baseURL:f}=a;let p=s.trim();e(s).forEach((e=>{c&&(p=p.replace(":"+e,""+c[e]))}));const[u,l]=p.split("?"),h=new URLSearchParams([...o(l),...o(i)]);let y=((e,o)=>{if(n(e))return e;const s=n(o)?o:r(o);return t(s)+t(r(e))})(u,f);return h.size&&(y=y+"?"+h),y},a=(...e)=>{const t=new Headers;return e.forEach((e=>{e&&new Headers(e).forEach(((e,r)=>{t.set(r,e)}))})),t};function i(e,t){const r=(e??[]).filter((e=>!!e));return t&&r.push(AbortSignal.timeout(t)),r.length?AbortSignal.any(r):void 0}function c(e){return!(!e||"object"!=typeof e||(e instanceof Blob||e instanceof ArrayBuffer||e instanceof FormData||e instanceof File||e instanceof DataView||e instanceof URLSearchParams||e instanceof ReadableStream||(t=e,t instanceof Int8Array||t instanceof Uint8Array||t instanceof Uint8ClampedArray||t instanceof Int16Array||t instanceof Uint16Array||t instanceof Int32Array||t instanceof Uint32Array||t instanceof Float32Array||t instanceof Float64Array||t instanceof BigInt64Array||t instanceof BigUint64Array)));var t}function f(e){const t=new FormData;return e&&"object"==typeof e&&!Array.isArray(e)&&Object.entries(e).forEach((([e,r])=>{let n;n=r instanceof Blob?r:JSON.stringify(r),t.append(e,n)})),t}function p(...e){const t=Object.assign({},...e),r=a(...e.map((e=>e?.headers)));return t.headers=r,t.signal=i(e.map((e=>e?.signal)),t.timeout),t}function u(e){const{url:t,options:r,baseURL:n,baseOptions:o}=e,a=p(o,r),f=s(t,{...a,baseURL:n}),u=a?.body,l=c(u);a.body=l?JSON.stringify(u):u,l&&a.headers.append("Content-Type","application/json");const h=new AbortController;return a.signal=i([a.signal,h.signal]),{url:f,options:a,is_body_json:l,abortController:h}}const l=["get","post","put","delete","patch"];function h(e,t){const r={};return e.forEach((e=>{r[e]=t(e)})),r}function y(t){const r=(r,n,o)=>{const s=!!e(r).length;return(...e)=>{const a=[...e],{hasBody:i,hasQuery:c,isFormData:p}=o||{},u=s?a.shift():void 0,l=c?a.shift():void 0;let h=i?a.shift():void 0;p&&(h=f(h));const y=a.shift();return t(r,n,u,l,h,y,o?.options)}},n={};return l.forEach((e=>{const t=e.toUpperCase();n[t]=t=>({Send:n=>r(t,e,{options:n}),Body:()=>({Send:n=>r(t,e,{hasBody:!0,options:n})}),FormData:()=>({Send:n=>r(t,e,{hasBody:!0,isFormData:!0,options:n})}),Query:()=>({Send:n=>r(t,e,{hasQuery:!0,options:n}),Body:()=>({Send:n=>r(t,e,{hasBody:!0,hasQuery:!0,options:n})}),FormData:()=>({Send:n=>r(t,e,{hasBody:!0,hasQuery:!0,isFormData:!0,options:n})})})})})),n}function d(e,t){t&&(t.pop()?.abort("race abort"),t.push(e))}function m(e){if(Array.isArray(e))return e.map(m);if("object"==typeof e&&null!==e){const t={};return Object.keys(e).sort().forEach((r=>{t[r]=m(e[r])})),t}return e}function g(e){const{url:t,options:r}=e,{headers:n,method:o,body:s,query:a,params:i}=r??{},c=m(Object.fromEntries(new Headers(n).entries()??[]));return(o??"get").toLowerCase()+t+JSON.stringify(m(a)??"")+JSON.stringify(m(i)??"")+JSON.stringify(c)+("object"==typeof s&&null!=s?JSON.stringify(m(s)):s??"")}function b(){const e={},t=[];setInterval((()=>{const r=Date.now();for(let n=t.length-1;n>=0;n--)t[n].expiredTime<r&&(delete e[t[n].key],t.splice(n,1))}),6e4);const r=e=>e instanceof Response?e.clone():"function"==typeof e||e instanceof Promise?e:structuredClone(e);function n(r){delete e[r];for(let e=t.length-1;e>=0;e--)if(t[e].key===r){t.splice(e,1);break}}return{get:function(t){const o=e[t];if(void 0!==o)return o.expiredTime>Date.now()?r(o.data):void n(t)},set:function(n,o,s){e[n]={data:r(o),expiredTime:s},t.push({key:n,expiredTime:s})},remove:n}}function A(){const e={},t=t=>e[t]=void 0;return{get:t=>e[t],set:(r,n)=>{e[r]=n,n.finally((()=>t(r)))}}}exports.createCache=b,exports.createShare=A,exports.createShortApi=y,exports.createShortMethods=h,exports.createSilentRefresh=function(e){let t=[],r=!1;return(n,o)=>{t.push({success:n,fail:o}),r||(r=!0,e().then((()=>{t.forEach((e=>e.success()))})).catch((e=>{t.forEach((e=>e.fail()))})).finally((()=>{r=!1,t=[]})))}},exports.createSoon=function(e,t){const r=b(),n=A(),o=(o,s)=>new Promise(((a,i)=>{const c=e(o,s),f=u({url:o,options:s,...c}),p=g(f),{abortController:l}=f,h=new AbortController;h.signal.addEventListener("abort",(()=>{i(h.signal.reason)}));const y=t({parsed:{...f,requestKey:p}});if(f.options?.share){const e=n.get(p);if(e)return a(e)}if(d(f.options.share?h:l,f.options?.aborts),f.options?.staleTime){const e=r.get(p);if(void 0!==e)return a(e)}const m=y(o,s);f.options?.share&&n.set(p,m),m.then((e=>{a(e),f.options?.staleTime&&r.set(p,e,(new Date).getTime()+f.options.staleTime)})).catch((e=>i(e)))})),s=y(((e,t,r,n,s,a,i)=>o(e,{...i,...a,method:t,params:r,query:n,body:s}))),a=h([...l,"head","options"],(e=>(t,r)=>o(t,{...r,method:e})));return{request:o,...s,...a}},exports.deepSort=m,exports.genRequestKey=g,exports.isBodyJson=c,exports.mergeHeaders=a,exports.mergeOptions=p,exports.mergeSignals=i,exports.mergeUrl=s,exports.parseUrlOptions=function(e){const{url:t,options:r}=u(e);return[t,r]},exports.parseWithBase=u,exports.raceAbort=d,exports.toFormData=f;
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ declare const mergeUrl: (url: string, config: {
|
|
|
21
21
|
declare const mergeHeaders: (...headersList: (HeadersInit | undefined)[]) => Headers;
|
|
22
22
|
declare function mergeSignals(signals?: (AbortSignal | null | undefined)[], timeout?: number): AbortSignal | undefined;
|
|
23
23
|
declare function isBodyJson(body: any): boolean;
|
|
24
|
+
declare function toFormData(body: any): FormData;
|
|
24
25
|
declare function mergeOptions<Options extends SoonOptions>(...optionsList: (Options | undefined)[]): Options & {
|
|
25
26
|
headers: Headers;
|
|
26
27
|
};
|
|
@@ -60,11 +61,17 @@ declare function createShortApi<Wrapper extends <T>(url: string, method: string,
|
|
|
60
61
|
Body: <Body>() => unknown extends Body ? never : {
|
|
61
62
|
Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
|
|
62
63
|
};
|
|
64
|
+
FormData: <Body>() => unknown extends Body ? never : {
|
|
65
|
+
Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
|
|
66
|
+
};
|
|
63
67
|
Query: <Query>() => unknown extends Query ? never : {
|
|
64
68
|
Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
|
|
65
69
|
Body: <Body>() => unknown extends Body ? never : {
|
|
66
70
|
Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
|
|
67
71
|
};
|
|
72
|
+
FormData: <Body>() => unknown extends Body ? never : {
|
|
73
|
+
Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
|
|
74
|
+
};
|
|
68
75
|
};
|
|
69
76
|
}>;
|
|
70
77
|
type NoData<T> = Omit<T, "method" | "body" | "params" | "query">;
|
|
@@ -91,7 +98,7 @@ declare function createShare(): {
|
|
|
91
98
|
};
|
|
92
99
|
declare function createSilentRefresh(refresh_token_fn: () => Promise<void>): (success: () => void, fail: () => void) => void;
|
|
93
100
|
declare function createSoon<Options extends SoonOptions>(getConfig: (url: string, options?: Options) => {
|
|
94
|
-
url
|
|
101
|
+
url?: string;
|
|
95
102
|
options?: Options;
|
|
96
103
|
baseURL?: string;
|
|
97
104
|
baseOptions?: Options;
|
|
@@ -125,11 +132,17 @@ declare function createSoon<Options extends SoonOptions>(getConfig: (url: string
|
|
|
125
132
|
Body: <Body>() => unknown extends Body ? never : {
|
|
126
133
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
127
134
|
};
|
|
135
|
+
FormData: <Body>() => unknown extends Body ? never : {
|
|
136
|
+
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
137
|
+
};
|
|
128
138
|
Query: <Query>() => unknown extends Query ? never : {
|
|
129
139
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
130
140
|
Body: <Body>() => unknown extends Body ? never : {
|
|
131
141
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
132
142
|
};
|
|
143
|
+
FormData: <Body>() => unknown extends Body ? never : {
|
|
144
|
+
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
145
|
+
};
|
|
133
146
|
};
|
|
134
147
|
};
|
|
135
148
|
PATCH: <Url extends string>(url: Url) => {
|
|
@@ -137,11 +150,17 @@ declare function createSoon<Options extends SoonOptions>(getConfig: (url: string
|
|
|
137
150
|
Body: <Body>() => unknown extends Body ? never : {
|
|
138
151
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
139
152
|
};
|
|
153
|
+
FormData: <Body>() => unknown extends Body ? never : {
|
|
154
|
+
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
155
|
+
};
|
|
140
156
|
Query: <Query>() => unknown extends Query ? never : {
|
|
141
157
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
142
158
|
Body: <Body>() => unknown extends Body ? never : {
|
|
143
159
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
144
160
|
};
|
|
161
|
+
FormData: <Body>() => unknown extends Body ? never : {
|
|
162
|
+
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
163
|
+
};
|
|
145
164
|
};
|
|
146
165
|
};
|
|
147
166
|
DELETE: <Url extends string>(url: Url) => {
|
|
@@ -149,11 +168,17 @@ declare function createSoon<Options extends SoonOptions>(getConfig: (url: string
|
|
|
149
168
|
Body: <Body>() => unknown extends Body ? never : {
|
|
150
169
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
151
170
|
};
|
|
171
|
+
FormData: <Body>() => unknown extends Body ? never : {
|
|
172
|
+
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
173
|
+
};
|
|
152
174
|
Query: <Query>() => unknown extends Query ? never : {
|
|
153
175
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
154
176
|
Body: <Body>() => unknown extends Body ? never : {
|
|
155
177
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
156
178
|
};
|
|
179
|
+
FormData: <Body>() => unknown extends Body ? never : {
|
|
180
|
+
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
181
|
+
};
|
|
157
182
|
};
|
|
158
183
|
};
|
|
159
184
|
PUT: <Url extends string>(url: Url) => {
|
|
@@ -161,14 +186,20 @@ declare function createSoon<Options extends SoonOptions>(getConfig: (url: string
|
|
|
161
186
|
Body: <Body>() => unknown extends Body ? never : {
|
|
162
187
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
163
188
|
};
|
|
189
|
+
FormData: <Body>() => unknown extends Body ? never : {
|
|
190
|
+
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
191
|
+
};
|
|
164
192
|
Query: <Query>() => unknown extends Query ? never : {
|
|
165
193
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
166
194
|
Body: <Body>() => unknown extends Body ? never : {
|
|
167
195
|
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
168
196
|
};
|
|
197
|
+
FormData: <Body>() => unknown extends Body ? never : {
|
|
198
|
+
Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
|
|
199
|
+
};
|
|
169
200
|
};
|
|
170
201
|
};
|
|
171
202
|
request: <T>(url: string, options?: Options) => Promise<T>;
|
|
172
203
|
};
|
|
173
204
|
|
|
174
|
-
export { type SoonOptions, createCache, createShare, createShortApi, createShortMethods, createSilentRefresh, createSoon, deepSort, genRequestKey, isBodyJson, mergeHeaders, mergeOptions, mergeSignals, mergeUrl, parseUrlOptions, parseWithBase, raceAbort };
|
|
205
|
+
export { type SoonOptions, createCache, createShare, createShortApi, createShortMethods, createSilentRefresh, createSoon, deepSort, genRequestKey, isBodyJson, mergeHeaders, mergeOptions, mergeSignals, mergeUrl, parseUrlOptions, parseWithBase, raceAbort, toFormData };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
const t=t=>{const e=[],n=t.match(/:([^:/\d]+)\/?/g);return n&&n.forEach((t=>{e.push(t.replace(/\//g,"").replace(/:/g,""))})),e},e=(t="")=>t.endsWith("/")?t.slice(0,-1):t,n=(t="")=>t.startsWith("/")?t:"/"+t,r=(t="")=>t.startsWith("http"),o=t=>{if(!t)return[];if(t instanceof URLSearchParams||"string"==typeof t||Array.isArray(t))return Array.from(new URLSearchParams(t).entries());const e=[];return Object.keys(t).forEach((n=>{const r=t[n];(Array.isArray(r)?r:[r]).forEach((t=>{e.push([n,t??""])}))})),e},s=(s,a)=>{const{query:i,params:c,baseURL:f}=a;let u=s.trim();t(s).forEach((t=>{c&&(u=u.replace(":"+t,""+c[t]))}));const[l,p]=u.split("?"),
|
|
1
|
+
const t=t=>{const e=[],n=t.match(/:([^:/\d]+)\/?/g);return n&&n.forEach((t=>{e.push(t.replace(/\//g,"").replace(/:/g,""))})),e},e=(t="")=>t.endsWith("/")?t.slice(0,-1):t,n=(t="")=>t.startsWith("/")?t:"/"+t,r=(t="")=>t.startsWith("http"),o=t=>{if(!t)return[];if(t instanceof URLSearchParams||"string"==typeof t||Array.isArray(t))return Array.from(new URLSearchParams(t).entries());const e=[];return Object.keys(t).forEach((n=>{const r=t[n];(Array.isArray(r)?r:[r]).forEach((t=>{e.push([n,t??""])}))})),e},s=(s,a)=>{const{query:i,params:c,baseURL:f}=a;let u=s.trim();t(s).forEach((t=>{c&&(u=u.replace(":"+t,""+c[t]))}));const[l,p]=u.split("?"),y=new URLSearchParams([...o(p),...o(i)]);let h=((t,o)=>{if(r(t))return t;const s=r(o)?o:n(o);return e(s)+e(n(t))})(l,f);return y.size&&(h=h+"?"+y),h},a=(...t)=>{const e=new Headers;return t.forEach((t=>{t&&new Headers(t).forEach(((t,n)=>{e.set(n,t)}))})),e};function i(t,e){const n=(t??[]).filter((t=>!!t));return e&&n.push(AbortSignal.timeout(e)),n.length?AbortSignal.any(n):void 0}function c(t){return!(!t||"object"!=typeof t||(t instanceof Blob||t instanceof ArrayBuffer||t instanceof FormData||t instanceof File||t instanceof DataView||t instanceof URLSearchParams||t instanceof ReadableStream||(e=t,e instanceof Int8Array||e instanceof Uint8Array||e instanceof Uint8ClampedArray||e instanceof Int16Array||e instanceof Uint16Array||e instanceof Int32Array||e instanceof Uint32Array||e instanceof Float32Array||e instanceof Float64Array||e instanceof BigInt64Array||e instanceof BigUint64Array)));var e}function f(t){const e=new FormData;return t&&"object"==typeof t&&!Array.isArray(t)&&Object.entries(t).forEach((([t,n])=>{let r;r=n instanceof Blob?n:JSON.stringify(n),e.append(t,r)})),e}function u(...t){const e=Object.assign({},...t),n=a(...t.map((t=>t?.headers)));return e.headers=n,e.signal=i(t.map((t=>t?.signal)),e.timeout),e}function l(t){const{url:e,options:n,baseURL:r,baseOptions:o}=t,a=u(o,n),f=s(e,{...a,baseURL:r}),l=a?.body,p=c(l);a.body=p?JSON.stringify(l):l,p&&a.headers.append("Content-Type","application/json");const y=new AbortController;return a.signal=i([a.signal,y.signal]),{url:f,options:a,is_body_json:p,abortController:y}}function p(t){const{url:e,options:n}=l(t);return[e,n]}const y=["get","post","put","delete","patch"];function h(t,e){const n={};return t.forEach((t=>{n[t]=e(t)})),n}function d(e){const n=(n,r,o)=>{const s=!!t(n).length;return(...t)=>{const a=[...t],{hasBody:i,hasQuery:c,isFormData:u}=o||{},l=s?a.shift():void 0,p=c?a.shift():void 0;let y=i?a.shift():void 0;u&&(y=f(y));const h=a.shift();return e(n,r,l,p,y,h,o?.options)}},r={};return y.forEach((t=>{const e=t.toUpperCase();r[e]=e=>({Send:r=>n(e,t,{options:r}),Body:()=>({Send:r=>n(e,t,{hasBody:!0,options:r})}),FormData:()=>({Send:r=>n(e,t,{hasBody:!0,isFormData:!0,options:r})}),Query:()=>({Send:r=>n(e,t,{hasQuery:!0,options:r}),Body:()=>({Send:r=>n(e,t,{hasBody:!0,hasQuery:!0,options:r})}),FormData:()=>({Send:r=>n(e,t,{hasBody:!0,hasQuery:!0,isFormData:!0,options:r})})})})})),r}function m(t,e){e&&(e.pop()?.abort("race abort"),e.push(t))}function g(t){if(Array.isArray(t))return t.map(g);if("object"==typeof t&&null!==t){const e={};return Object.keys(t).sort().forEach((n=>{e[n]=g(t[n])})),e}return t}function b(t){const{url:e,options:n}=t,{headers:r,method:o,body:s,query:a,params:i}=n??{},c=g(Object.fromEntries(new Headers(r).entries()??[]));return(o??"get").toLowerCase()+e+JSON.stringify(g(a)??"")+JSON.stringify(g(i)??"")+JSON.stringify(c)+("object"==typeof s&&null!=s?JSON.stringify(g(s)):s??"")}function A(){const t={},e=[];setInterval((()=>{const n=Date.now();for(let r=e.length-1;r>=0;r--)e[r].expiredTime<n&&(delete t[e[r].key],e.splice(r,1))}),6e4);const n=t=>t instanceof Response?t.clone():"function"==typeof t||t instanceof Promise?t:structuredClone(t);function r(n){delete t[n];for(let t=e.length-1;t>=0;t--)if(e[t].key===n){e.splice(t,1);break}}return{get:function(e){const o=t[e];if(void 0!==o)return o.expiredTime>Date.now()?n(o.data):void r(e)},set:function(r,o,s){t[r]={data:n(o),expiredTime:s},e.push({key:r,expiredTime:s})},remove:r}}function S(){const t={},e=e=>t[e]=void 0;return{get:e=>t[e],set:(n,r)=>{t[n]=r,r.finally((()=>e(n)))}}}function w(t){let e=[],n=!1;return(r,o)=>{e.push({success:r,fail:o}),n||(n=!0,t().then((()=>{e.forEach((t=>t.success()))})).catch((t=>{e.forEach((t=>t.fail()))})).finally((()=>{n=!1,e=[]})))}}function E(t,e){const n=A(),r=S(),o=(o,s)=>new Promise(((a,i)=>{const c=t(o,s),f=l({url:o,options:s,...c}),u=b(f),{abortController:p}=f,y=new AbortController;y.signal.addEventListener("abort",(()=>{i(y.signal.reason)}));const h=e({parsed:{...f,requestKey:u}});if(f.options?.share){const t=r.get(u);if(t)return a(t)}if(m(f.options.share?y:p,f.options?.aborts),f.options?.staleTime){const t=n.get(u);if(void 0!==t)return a(t)}const d=h(o,s);f.options?.share&&r.set(u,d),d.then((t=>{a(t),f.options?.staleTime&&n.set(u,t,(new Date).getTime()+f.options.staleTime)})).catch((t=>i(t)))})),s=d(((t,e,n,r,s,a,i)=>o(t,{...i,...a,method:e,params:n,query:r,body:s}))),a=h([...y,"head","options"],(t=>(e,n)=>o(e,{...n,method:t})));return{request:o,...s,...a}}export{A as createCache,S as createShare,d as createShortApi,h as createShortMethods,w as createSilentRefresh,E as createSoon,g as deepSort,b as genRequestKey,c as isBodyJson,a as mergeHeaders,u as mergeOptions,i as mergeSignals,s as mergeUrl,p as parseUrlOptions,l as parseWithBase,m as raceAbort,f as toFormData};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "soon-fetch",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "a 5Kb request lib alternative to axios with timeout, request reusing, race, response cache ...",
|
|
5
5
|
"homepage": "https://github.com/leafio/soon-fetch",
|
|
6
6
|
"main": "./dist/index.cjs.js",
|