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 CHANGED
@@ -36,17 +36,20 @@
36
36
  import { createSoon } from "soon-fetch";
37
37
 
38
38
  const soon = createSoon(
39
- (url, options) => ({
40
- url,
41
- options,
42
- baseURL: "/api",
43
- baseOptions: {
44
- headers: new Headers({
45
- Authorization: "Bearer " + localStorage.getItem("token"),
46
- }),
47
- timeout: 5000,
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
- **极轻量的请求库,不到 3K**
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
- url,
248
- options,
249
- baseURL: "/api",
250
- baseOptions: {
251
- headers: new Headers({
252
- Authorization: "Bearer " + localStorage.getItem("token"),
253
- }),
254
- timeout: 5000,
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 p(e){const{url:t,options:r,baseURL:n,baseOptions:o}=e,a=f(o,r),p=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:p,options:a,is_body_json:l,abortController:h}}const u=["get","post","put","delete","patch"];function l(e,t){const r={};return e.forEach((e=>{r[e]=t(e)})),r}function h(t){const r=(r,n,o)=>{const s=!!e(r).length;return(...e)=>{const a=[...e],{hasBody:i,hasQuery:c}=o||{},f=s?a.shift():void 0,p=c?a.shift():void 0,u=i?a.shift():void 0,l=a.shift();return t(r,n,f,p,u,l,o?.options)}},n={};return u.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})}),Query:()=>({Send:n=>r(t,e,{hasQuery:!0,options:n}),Body:()=>({Send:n=>r(t,e,{hasBody:!0,hasQuery:!0,options:n})})})})})),n}function y(e,t){t&&(t.pop()?.abort("race abort"),t.push(e))}function d(e){if(Array.isArray(e))return e.map(d);if("object"==typeof e&&null!==e){const t={};return Object.keys(e).sort().forEach((r=>{t[r]=d(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=d(Object.fromEntries(new Headers(n).entries()??[]));return(o??"get").toLowerCase()+t+JSON.stringify(d(a)??"")+JSON.stringify(d(i)??"")+JSON.stringify(c)+("object"==typeof s&&null!=s?JSON.stringify(d(s)):s??"")}function m(){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 b(){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=m,exports.createShare=b,exports.createShortApi=h,exports.createShortMethods=l,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=m(),n=b(),o=(o,s)=>new Promise(((a,i)=>{const c=p(e(o,s)),f=g(c),{abortController:u}=c,l=new AbortController;l.signal.addEventListener("abort",(()=>{i(l.signal.reason)}));const h=t({parsed:{...c,requestKey:f}});if(c.options?.share){const e=n.get(f);if(e)return a(e)}if(y(c.options.share?l:u,c.options?.aborts),c.options?.staleTime){const e=r.get(f);if(void 0!==e)return a(e)}const d=h(o,s);c.options?.share&&n.set(f,d),d.then((e=>{a(e),c.options?.staleTime&&r.set(f,e,(new Date).getTime()+c.options.staleTime)})).catch((e=>i(e)))})),s=h(((e,t,r,n,s,a,i)=>o(e,{...i,...a,method:t,params:r,query:n,body:s}))),a=l([...u,"head","options"],(e=>(t,r)=>o(t,{...r,method:e})));return{request:o,...s,...a}},exports.deepSort=d,exports.genRequestKey=g,exports.isBodyJson=c,exports.mergeHeaders=a,exports.mergeOptions=f,exports.mergeSignals=i,exports.mergeUrl=s,exports.parseUrlOptions=function(e){const{url:t,options:r}=p(e);return[t,r]},exports.parseWithBase=p,exports.raceAbort=y;
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: string;
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("?"),h=new URLSearchParams([...o(p),...o(i)]);let y=((t,o)=>{if(r(t))return t;const s=r(o)?o:n(o);return e(s)+e(n(t))})(l,f);return h.size&&(y=y+"?"+h),y},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=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 u(t){const{url:e,options:n,baseURL:r,baseOptions:o}=t,a=f(o,n),u=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 h=new AbortController;return a.signal=i([a.signal,h.signal]),{url:u,options:a,is_body_json:p,abortController:h}}function l(t){const{url:e,options:n}=u(t);return[e,n]}const p=["get","post","put","delete","patch"];function h(t,e){const n={};return t.forEach((t=>{n[t]=e(t)})),n}function y(e){const n=(n,r,o)=>{const s=!!t(n).length;return(...t)=>{const a=[...t],{hasBody:i,hasQuery:c}=o||{},f=s?a.shift():void 0,u=c?a.shift():void 0,l=i?a.shift():void 0,p=a.shift();return e(n,r,f,u,l,p,o?.options)}},r={};return p.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})}),Query:()=>({Send:r=>n(e,t,{hasQuery:!0,options:r}),Body:()=>({Send:r=>n(e,t,{hasBody:!0,hasQuery:!0,options:r})})})})})),r}function d(t,e){e&&(e.pop()?.abort("race abort"),e.push(t))}function m(t){if(Array.isArray(t))return t.map(m);if("object"==typeof t&&null!==t){const e={};return Object.keys(t).sort().forEach((n=>{e[n]=m(t[n])})),e}return t}function g(t){const{url:e,options:n}=t,{headers:r,method:o,body:s,query:a,params:i}=n??{},c=m(Object.fromEntries(new Headers(r).entries()??[]));return(o??"get").toLowerCase()+e+JSON.stringify(m(a)??"")+JSON.stringify(m(i)??"")+JSON.stringify(c)+("object"==typeof s&&null!=s?JSON.stringify(m(s)):s??"")}function b(){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 A(){const t={},e=e=>t[e]=void 0;return{get:e=>t[e],set:(n,r)=>{t[n]=r,r.finally((()=>e(n)))}}}function S(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 w(t,e){const n=b(),r=A(),o=(o,s)=>new Promise(((a,i)=>{const c=u(t(o,s)),f=g(c),{abortController:l}=c,p=new AbortController;p.signal.addEventListener("abort",(()=>{i(p.signal.reason)}));const h=e({parsed:{...c,requestKey:f}});if(c.options?.share){const t=r.get(f);if(t)return a(t)}if(d(c.options.share?p:l,c.options?.aborts),c.options?.staleTime){const t=n.get(f);if(void 0!==t)return a(t)}const y=h(o,s);c.options?.share&&r.set(f,y),y.then((t=>{a(t),c.options?.staleTime&&n.set(f,t,(new Date).getTime()+c.options.staleTime)})).catch((t=>i(t)))})),s=y(((t,e,n,r,s,a,i)=>o(t,{...i,...a,method:e,params:n,query:r,body:s}))),a=h([...p,"head","options"],(t=>(e,n)=>o(e,{...n,method:t})));return{request:o,...s,...a}}export{b as createCache,A as createShare,y as createShortApi,h as createShortMethods,S as createSilentRefresh,w as createSoon,m as deepSort,g as genRequestKey,c as isBodyJson,a as mergeHeaders,f as mergeOptions,i as mergeSignals,s as mergeUrl,l as parseUrlOptions,u as parseWithBase,d as raceAbort};
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.0.0",
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",