soon-fetch 3.0.0-beta.1 → 3.0.0-beta.2

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
@@ -1,404 +1,390 @@
1
- [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
2
-
3
- <!-- omit in toc -->
4
-
5
- ### `soon-fetch`
6
-
7
- **A lightweight http request lib , alternative to axios**
8
-
9
- > - 🌐 automatic parse restful api url parameters
10
- > - ⭐ rapid define a request api
11
- > - ⌛ timeout disconnect
12
- > - 🔤 automatic parse or serialization of JSON
13
- > - 📏 .min size less than **3K**, smaller after zip
14
- > - 💡 smart type tips with Typescript
15
-
16
- - [Example](#example)
17
-
18
- - [Features](#features)
19
- - [Shortcut](#shortcut)
20
- - [Restful Url Params](#restful-url-params)
21
- - [Timeout](#timeout)
22
- - [Rapid Define APIs](#rapid-define-apis)
23
- - [API](#api)
24
- - [Support Me](#support-me)
25
-
26
- ### Example
27
-
28
- > [github: soon-admin-vue3 ](https://github.com/leafio/soon-admin-vue3)
29
- > [github: soon-admin-react-nextjs ](https://github.com/leafio/soon-admin-react-nextjs)
30
-
31
- ```typescript
32
- import { createSoon, parseUrlOptions, type SoonOptions } from "soon-fetch";
33
-
34
- const request = <T>(url: string, options?: SoonOptions):Promise<T> => {
35
- const [_url, _options] = parseUrlOptions({
36
- url,
37
- options,
38
- baseURL: "/api",
39
- baseOptions: {
40
- timeout: 20 * 1000,
41
- headers: { Authorization: localStorage.getItem("token") ?? "" },
42
- },
43
- });
44
-
45
- return fetch(_url, _options).then((res) => res.json());
46
- };
47
-
48
- const soon = createSoon(request);
49
-
50
- /** GET */
51
- soon.get("/user?id=123");
52
- soon.get("/user", { query: { id: 123 } });
53
- soon.get("/user/:id", { params: { id: 123 } });
54
-
55
- /** POST */
56
- soon.post("/login", { body: { username: "admin", password: "123456" } });
57
-
58
- /**Define API */
59
- export const login = soon
60
- .API("/user/login")
61
- .POST<{ username: string; password: string }, { token: string }>();
62
-
63
- login({ username: "admin", password: "123" }).then((res) => {
64
- localStorage.setItem("token", res.token);
65
- });
66
- ```
67
-
68
- ### Features
69
-
70
- ##### Shortcut
71
-
72
- ```typescript
73
- soon.get(url, options);
74
- soon.post(url, options);
75
- soon.put(url, options);
76
- soon.patch(url, options);
77
- soon.delete(url, options);
78
- soon.head(url, options);
79
- soon.options(url, options);
80
- ```
81
-
82
- ##### Restful Url Params
83
-
84
- url like /:key , will handle the key
85
-
86
- ```typescript
87
- soon.get("/api/user/:id", { params: { id: 1 } });
88
- // api/user/1
89
- soon.get("/api/:job/:year", { params: { job: "engineer", year: 5 } });
90
- //api/engineer/5
91
- ```
92
-
93
- ##### Timeout
94
-
95
- ```typescript
96
- //** the request level timeout, will override the instance level timeout */
97
- soon.get(url, { timeout: 1000 * 20 });
98
- ```
99
-
100
- ##### Rapid Define APIs
101
-
102
- ```typescript
103
- //can be GET POST PATCH PUT DELETE
104
- //GET data=>query,other method data=>body
105
- soon.API(url:string).POST<RequestType,ResponseType>()
106
-
107
- //define an api
108
- export const getUserInfo=soon.API('/user/:id').GET()
109
- //then use in any where
110
- getUserInfo({id:2}).then(res=>console.log(res))
111
-
112
-
113
- //with typescript,
114
- export const login=soon.API('/user/login')
115
- .POST<{username:string,password:string},{token:string}>()
116
- //the develop tools will have type tips for request and response
117
- login({username:'admin',password:'123'}).then(res=>{
118
- localStorage.setItem('token', res.token);
119
- })
120
- ```
121
-
122
- ### API
123
-
124
- #### SoonOptions
125
-
126
- ```ts
127
- // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
128
- // RequestInit is fetch's init options
129
- type SoonOptions = Omit<RequestInit, "body"> & {
130
- query?:
131
- | Record<
132
- string,
133
- | string
134
- | number
135
- | boolean
136
- | null
137
- | undefined
138
- | (string | number | boolean | null | undefined)[]
139
- >
140
- | URLSearchParams;
141
- params?: Record<string, string | number>;
142
- timeout?: number;
143
- body?: RequestInit["body"] | object;
144
- };
145
- ```
146
-
147
- #### parseUrlOptions
148
-
149
- `parseUrlOptions` source code:
150
-
151
- ```ts
152
- function parseUrlOptions<Options extends SoonOptions>(urlOptions: {
153
- url: string;
154
- options?: Options;
155
- baseURL?: string;
156
- baseOptions?: Options;
157
- }) {
158
- const { url, options, baseURL, baseOptions } = urlOptions;
159
- //override baseOptions
160
- const _options = { ...baseOptions, ...options };
161
-
162
- //signal merge signals by AbortSignal.any
163
- _options.signal = mergeSignals(
164
- [baseOptions?.signal, options?.signal],
165
- _options.timeout
166
- );
167
-
168
- //url handled with baseURL , options.query , options.params
169
- const _url = mergeUrl(url, { ..._options, baseURL });
170
-
171
- //body auto stringify json body
172
- let _body = options?.body;
173
- let is_body_json = isBodyJson(_body);
174
- _options.body = is_body_json ? JSON.stringify(_body) : _body;
175
-
176
- //headers merge headers , the same-key header would be override by options.headers
177
- //if body is json ,then add header "Content-Type": "application/json" }
178
- const headers = mergeHeaders(
179
- baseOptions?.headers,
180
- options?.headers,
181
- is_body_json ? { "Content-Type": "application/json" } : undefined
182
- );
183
- _options.headers = headers;
184
-
185
- return [_url, _options as Options & { headers: Headers }] as const;
186
- }
187
- ```
188
-
189
- You can customize your own parse function with the functions exported below:
190
- `mergeHeaders`, `mergeSignals`, `mergeUrl`, `isBodyJson`
191
-
192
- ### Support Me
193
-
194
- If you like this library , you can give a **star** on github.
195
- GitHub: https://github.com/leafio/soon-fetch
196
-
197
- > Email: leafnote@outlook.com
198
-
199
- [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
200
-
201
- <!-- omit in toc -->
202
-
203
- #### soon-fetch
204
-
205
- **极轻量的请求库,不到 3K**
206
-
207
- > - 🌐 自动解析 rest Url 的参数
208
- > - ⭐ 快捷定义请求 api
209
- > - ⌛ 超时断开
210
- > - 🔤 自动处理 JSON
211
- > - 📏 不到 **3K** , zip 后会更小
212
- > - 💡 用 typescript 有智能类型提醒
213
-
214
- - [示例](#示例)
215
-
216
- - [特别功能](#特别功能)
217
-
218
- - [快捷方法](#快捷方法)
219
- - [Restful Url 参数自动处理](#restful-url-参数自动处理)
220
- - [超时](#超时)
221
- - [快速定义 API](#快速定义-api)
222
-
223
- - [API](#api-1)
224
- - [支持一下](#支持一下)
225
-
226
- ### 示例
227
-
228
- > [github: soon-admin-vue3 ](https://github.com/leafio/soon-admin-vue3)
229
- > [github: soon-admin-react-nextjs ](https://github.com/leafio/soon-admin-react-nextjs)
230
-
231
- ```typescript
232
- const request = <T>(url: string, options?: SoonOptions): Promise<T> => {
233
- const [_url, _options] = parseUrlOptions({
234
- url,
235
- options,
236
- baseURL: "/api",
237
- baseOptions: {
238
- timeout: 20 * 1000,
239
- headers: { Authorization: localStorage.getItem("token") ?? "" },
240
- },
241
- });
242
-
243
- return fetch(_url, _options).then((res) => res.json());
244
- };
245
-
246
- const soon = createSoon(request);
247
-
248
- /** GET */
249
- soon.get("/user?id=123");
250
- soon.get("/user", { query: { id: 123 } });
251
- soon.get("/user/:id", { params: { id: 123 } });
252
-
253
- /** POST */
254
- soon.post("/login", { body: { username: "admin", password: "123456" } });
255
-
256
- /**定义 API */
257
- export const login = soon
258
- .API("/user/login")
259
- .POST<{ username: string; password: string }, { token: string }>();
260
-
261
- login({ username: "admin", password: "123" }).then((res) => {
262
- localStorage.setItem("token", res.token);
263
- });
264
- ```
265
-
266
- ### 特别功能
267
-
268
- ##### 快捷方法
269
-
270
- ```typescript
271
- soon.get(url, options);
272
- soon.post(url, options);
273
- soon.put(url, options);
274
- soon.patch(url, options);
275
- soon.delete(url, options);
276
- soon.head(url, options);
277
- soon.options(url, options);
278
- ```
279
-
280
- ###### Restful Url 参数自动处理
281
-
282
- url 包含 /:key 会解析匹配 key
283
-
284
- ```typescript
285
- soon.get("/api/user/:id", { params: { id: 1 } });
286
- // api/user/1
287
- soon.get("/api/:job/:year", { params: { job: "engineer", year: 5 } });
288
- //api/engineer/5
289
- ```
290
-
291
- ##### 超时
292
-
293
- ```typescript
294
- //** 请求级超时, 会覆盖实例级超时 */
295
- soon.get(url, { timeout: 1000 * 20 });
296
- ```
297
-
298
- ##### 快速定义 API
299
-
300
- ```typescript
301
- //可以是 GET POST PATCH PUT DELETE
302
- //GET 请求数据传递至query,其他方法请求数据传递至body
303
- soon.API(url:string).POST<RequestType,ResponseType>()
304
-
305
- //定义一个api
306
- export const getUserInfo=soon.API('/user/:id').GET()
307
- //使用
308
- getUserInfo({id:2}).then(res=>console.log(res))
309
-
310
- //用typescript,
311
- export const login=soon.API('/user/login')
312
- .POST<{username:string,password:string},{token:string}>()
313
- //开发工具会有请求和响应的智能提醒
314
- login({username:'admin',password:'123'}).then(res=>{
315
- localStorage.setItem('token', res.token);
316
- })
317
- ```
318
-
319
- ### API
320
-
321
- #### SoonOptions
322
-
323
- ```ts
324
- // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
325
- // RequestInit 为原生 fetch init 选项
326
- type SoonOptions = Omit<RequestInit, "body"> & {
327
- query?:
328
- | Record<
329
- string,
330
- | string
331
- | number
332
- | boolean
333
- | null
334
- | undefined
335
- | (string | number | boolean | null | undefined)[]
336
- >
337
- | URLSearchParams;
338
- params?: Record<string, string | number>;
339
- timeout?: number;
340
- body?: RequestInit["body"] | object;
341
- };
342
- ```
343
- #### parseUrlOptions
344
-
345
- `parseUrlOptions` 源码如下:
346
-
347
- ```ts
348
- function parseUrlOptions<Options extends SoonOptions>(urlOptions: {
349
- url: string;
350
- options?: Options;
351
- baseURL?: string;
352
- baseOptions?: Options;
353
- }) {
354
- const { url, options, baseURL, baseOptions } = urlOptions;
355
- // 覆盖 baseOptions
356
- const _options = { ...baseOptions, ...options };
357
-
358
- // 以 AbortSignal.any 的方式合并 baseOptions.signal 、 options.signal 、
359
- // 以及 AbortSignal.timeout( _options.timeout)
360
- _options.signal = mergeSignals(
361
- [baseOptions?.signal, options?.signal],
362
- _options.timeout
363
- );
364
-
365
- //根据 baseURL , options.query , options.params 解析出完整的url
366
- const _url = mergeUrl(url, { ..._options, baseURL });
367
-
368
- // 自动stringify json-object类的body
369
- let _body = options?.body;
370
- let is_body_json = isBodyJson(_body);
371
- _options.body = is_body_json ? JSON.stringify(_body) : _body;
372
-
373
- //合并headers, 相同key值的header会被options.headers覆盖
374
- //当body 为 json ,自动添加 header "Content-Type": "application/json" }
375
- const headers = mergeHeaders(
376
- baseOptions?.headers,
377
- options?.headers,
378
- is_body_json ? { "Content-Type": "application/json" } : undefined
379
- );
380
- _options.headers = headers;
381
-
382
- return [_url, _options as Options & { headers: Headers }] as const;
383
- }
384
- ```
385
-
386
- 如有特殊需要,可以根据下方的函数定制你自己的解析函数来替代 `parseUrlOptions`:
387
- `mergeHeaders`, `mergeSignals`, `mergeUrl`, `isBodyJson`
388
-
389
- ### 支持一下
390
-
391
- 喜欢 soon-fetch 的话 , 在 github 上给个 **star** 吧.
392
- GitHub: https://github.com/leafio/soon-fetch
393
-
394
- > Email: leafnote@outlook.com
395
-
396
- [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
397
-
398
- <!-- omit in toc -->
399
-
400
- ##### 安装 Installation
401
-
402
- ```bash
403
- npm install soon-fetch
404
- ```
1
+ [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
2
+
3
+ <!-- omit in toc -->
4
+
5
+ ### soon-fetch
6
+
7
+ **A lightweight http request lib , alternative to axios**
8
+
9
+ > - 🌐 automatic parse restful api url parameters
10
+ > - ⭐ rapid define a request api
11
+ > - ⌛ timeout disconnect
12
+ > - 🔤 automatic parse or serialization of JSON
13
+ > - 📏 .min size less than **3K**, smaller after zip
14
+ > - 💡 smart type tips with Typescript
15
+
16
+ - [Example](#example)
17
+ - [Features](#features)
18
+ - [Shortcut](#shortcut)
19
+ - [Restful Url Params](#restful-url-params)
20
+ - [Timeout](#timeout)
21
+ - [Rapid Define APIs](#rapid-define-apis)
22
+ - [API](#api)
23
+
24
+
25
+ ### Example
26
+
27
+ > [github: soon-admin-vue3 ](https://github.com/leafio/soon-admin-vue3)
28
+ > [github: soon-admin-react-nextjs ](https://github.com/leafio/soon-admin-react-nextjs)
29
+
30
+ ```typescript
31
+ import { createSoon, parseUrlOptions, type SoonOptions } from "soon-fetch";
32
+
33
+ const soon = createSoon(({ abortController }) => {
34
+ return <T>(url: string, options?: SoonOptions) => {
35
+ const [_url, _options] = parseUrlOptions({
36
+ url,
37
+ options,
38
+ baseURL: "/api",
39
+ baseOptions: {
40
+ headers: new Headers({
41
+ Authorization: "Bearer " + localStorage.getItem("token"),
42
+ }),
43
+ timeout: 5000,
44
+ signal: abortController.signal,
45
+ },
46
+ });
47
+ return fetch(_url, _options).then((res) => res.json()) as Promise<T>;
48
+ };
49
+ });
50
+
51
+ /** GET */
52
+ soon.get("/user?id=123");
53
+ soon.get("/user", { query: { id: 123 } });
54
+ soon.get("/user/:id", { params: { id: 123 } });
55
+
56
+ /** POST */
57
+ soon.post("/login", { body: { username: "admin", password: "123456" } });
58
+
59
+ /**Define API */
60
+ export const login = soon
61
+ .API("/user/login")
62
+ .POST<{ username: string; password: string }, { token: string }>();
63
+
64
+ login({ username: "admin", password: "123" }).then((res) => {
65
+ localStorage.setItem("token", res.token);
66
+ });
67
+ ```
68
+
69
+ ### Features
70
+
71
+ ##### Shortcut
72
+
73
+ ```typescript
74
+ soon.get(url, options);
75
+ soon.post(url, options);
76
+ soon.put(url, options);
77
+ soon.patch(url, options);
78
+ soon.delete(url, options);
79
+ soon.head(url, options);
80
+ soon.options(url, options);
81
+ ```
82
+
83
+ ##### Restful Url Params
84
+
85
+ url like /:key , will handle the key
86
+
87
+ ```typescript
88
+ soon.get("/api/user/:id", { params: { id: 1 } });
89
+ // api/user/1
90
+ soon.get("/api/:job/:year", { params: { job: "engineer", year: 5 } });
91
+ //api/engineer/5
92
+ ```
93
+
94
+ ##### Timeout
95
+
96
+ ```typescript
97
+ //** the request level timeout, will override the instance level timeout */
98
+ soon.get(url, { timeout: 1000 * 20 });
99
+ ```
100
+
101
+ ##### Share pending request
102
+
103
+ If a request is made again before the first completes, will reuse the first request instead of making a new request.
104
+
105
+ ```ts
106
+ soon.get(url, { share: true });
107
+ ```
108
+
109
+ ##### Cache response
110
+
111
+ A cached response will be returned if the request is made again within the specified time.
112
+
113
+ ```ts
114
+ soon.get(url, { staleTime: 1000 * 60 * 5 });
115
+ ```
116
+
117
+ ##### Request race
118
+
119
+ ‌If a second request is made before the first completes, abort the first to avoid race conditions from out-of-order responses.
120
+
121
+ ```tsx
122
+ type User = { name: string; job: string };
123
+ const api = soon.GET("/api/users").Query<{ page: number }>().Send<User[]>();
124
+ export default function App() {
125
+ const refAbort = useRef<AbortController[]>([]);
126
+ const [list, setList] = useState<User[]>([]);
127
+ const [page, setPage] = useState(1);
128
+ useEffect(() => {
129
+ api({ page }).then(setList).catch(console.log);
130
+ }, [page]);
131
+ return (
132
+ <div>
133
+ <button onClick={() => setPage((pre) => pre + 1)}>next</button>
134
+ <div>
135
+ {list.map((item) => (
136
+ <div key={item.name}>{item.name}</div>
137
+ ))}
138
+ </div>
139
+ </div>
140
+ );
141
+ }
142
+ ```
143
+
144
+ ##### Rapid Define APIs
145
+
146
+ ```typescript
147
+ //can be GET POST PATCH PUT DELETE
148
+
149
+ //define an api
150
+ export const getUserInfo = soon.GET("/user/:id").Send();
151
+ //then use in any where
152
+ getUserInfo({ id: 2 }).then((res) => console.log(res));
153
+
154
+ //with typescript,
155
+ export const login = soon
156
+ .POST("/user/login")
157
+ .Body<{ username: string; password: string }>()
158
+ .Send<{ token: string }>();
159
+ //the develop tools will have type tips for request and response
160
+ login({ username: "admin", password: "123" }).then((res) => {
161
+ localStorage.setItem("token", res.token);
162
+ });
163
+ ```
164
+
165
+ ### API
166
+
167
+ #### SoonOptions
168
+
169
+ ```ts
170
+ // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
171
+ // RequestInit is fetch's init options
172
+ type SoonOptions = Omit<RequestInit, "body"> & {
173
+ query?:
174
+ | Record<
175
+ string,
176
+ | string
177
+ | number
178
+ | boolean
179
+ | null
180
+ | undefined
181
+ | (string | number | boolean | null | undefined)[]
182
+ >
183
+ | URLSearchParams;
184
+ params?: Record<string, string | number>;
185
+ timeout?: number;
186
+ body?: RequestInit["body"] | object;
187
+ aborts?: AbortController[];
188
+ share?: boolean;
189
+ staleTime?: number;
190
+ };
191
+ ```
192
+
193
+ [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
194
+
195
+ <!-- omit in toc -->
196
+
197
+ #### soon-fetch
198
+
199
+ **极轻量的请求库,不到 3K**
200
+
201
+ > - 🌐 自动解析 rest Url 的参数
202
+ > - ⭐ 快捷定义请求 api
203
+ > - ⌛ 超时断开
204
+ > - 🔤 自动处理 JSON
205
+ > - 📏 不到 **3K** , zip 后会更小
206
+ > - 💡 用 typescript 有智能类型提醒
207
+
208
+ - [示例](#示例)
209
+
210
+ - [特别功能](#特别功能)
211
+
212
+ - [快捷方法](#快捷方法)
213
+ - [Restful Url 参数自动处理](#restful-url-参数自动处理)
214
+ - [超时](#超时)
215
+ - [快速定义 API](#快速定义-api)
216
+
217
+ - [API](#api-1)
218
+
219
+ ### 示例
220
+
221
+ > [github: soon-admin-vue3 ](https://github.com/leafio/soon-admin-vue3)
222
+ > [github: soon-admin-react-nextjs ](https://github.com/leafio/soon-admin-react-nextjs)
223
+
224
+ ```typescript
225
+ const request = <T>(url: string, options?: SoonOptions): Promise<T> => {
226
+ const [_url, _options] = parseUrlOptions({
227
+ url,
228
+ options,
229
+ baseURL: "/api",
230
+ baseOptions: {
231
+ timeout: 20 * 1000,
232
+ headers: { Authorization: localStorage.getItem("token") ?? "" },
233
+ },
234
+ });
235
+
236
+ return fetch(_url, _options).then((res) => res.json());
237
+ };
238
+
239
+ const soon = createSoon(request);
240
+
241
+ /** GET */
242
+ soon.get("/user?id=123");
243
+ soon.get("/user", { query: { id: 123 } });
244
+ soon.get("/user/:id", { params: { id: 123 } });
245
+
246
+ /** POST */
247
+ soon.post("/login", { body: { username: "admin", password: "123456" } });
248
+
249
+ /**定义 API */
250
+ export const login = soon
251
+ .API("/user/login")
252
+ .POST<{ username: string; password: string }, { token: string }>();
253
+
254
+ login({ username: "admin", password: "123" }).then((res) => {
255
+ localStorage.setItem("token", res.token);
256
+ });
257
+ ```
258
+
259
+ ### 特别功能
260
+
261
+ ##### 快捷方法
262
+
263
+ ```typescript
264
+ soon.get(url, options);
265
+ soon.post(url, options);
266
+ soon.put(url, options);
267
+ soon.patch(url, options);
268
+ soon.delete(url, options);
269
+ soon.head(url, options);
270
+ soon.options(url, options);
271
+ ```
272
+
273
+ ###### Restful Url 参数自动处理
274
+
275
+ url 包含 /:key 会解析匹配 key
276
+
277
+ ```typescript
278
+ soon.get("/api/user/:id", { params: { id: 1 } });
279
+ // api/user/1
280
+ soon.get("/api/:job/:year", { params: { job: "engineer", year: 5 } });
281
+ //api/engineer/5
282
+ ```
283
+
284
+ ##### 超时
285
+
286
+ ```typescript
287
+ //** 请求级超时, 会覆盖实例级超时 */
288
+ soon.get(url, { timeout: 1000 * 20 });
289
+ ```
290
+
291
+ ##### 共享未完成的请求
292
+
293
+ 如果在第一个请求完成之前再次发起相同的请求,则会复用第一个请求,而不是发起新的请求。
294
+
295
+ ```ts
296
+ soon.get(url, { share: true });
297
+ ```
298
+
299
+ ##### 缓存响应
300
+
301
+ 如果在指定时间内再次发起相同的请求,则会返回缓存的响应。
302
+
303
+ ```ts
304
+ soon.get(url, { staleTime: 1000 * 60 * 5 });
305
+ ```
306
+
307
+ ##### 请求竞争
308
+
309
+ 如果在第一个请求完成之前发起第二个请求,则会中止第一个请求,以避免因响应顺序错乱导致的问题。
310
+
311
+ ```tsx
312
+ type User = { name: string; job: string };
313
+ const api = soon.GET("/api/users").Query<{ page: number }>().Send<User[]>();
314
+ export default function App() {
315
+ const refAbort = useRef<AbortController[]>([]);
316
+ const [list, setList] = useState<User[]>([]);
317
+ const [page, setPage] = useState(1);
318
+ useEffect(() => {
319
+ api({ page }, { aborts: refAbort.current })
320
+ .then(setList)
321
+ .catch(console.log);
322
+ }, [page]);
323
+ return (
324
+ <div>
325
+ <button onClick={() => setPage((pre) => pre + 1)}>next</button>
326
+ <div>
327
+ {list.map((item) => (
328
+ <div key={item.name}>{item.name}</div>
329
+ ))}
330
+ </div>
331
+ </div>
332
+ );
333
+ }
334
+ ```
335
+
336
+ ##### 快速定义 API
337
+
338
+ ```typescript
339
+ //可以是 GET POST PATCH PUT DELETE
340
+ //GET 请求数据传递至query,其他方法请求数据传递至body
341
+ soon.API(url:string).POST<RequestType,ResponseType>()
342
+
343
+ //定义一个api
344
+ export const getUserInfo=soon.API('/user/:id').GET()
345
+ //使用
346
+ getUserInfo({id:2}).then(res=>console.log(res))
347
+
348
+ //用typescript,
349
+ export const login=soon.API('/user/login')
350
+ .POST<{username:string,password:string},{token:string}>()
351
+ //开发工具会有请求和响应的智能提醒
352
+ login({username:'admin',password:'123'}).then(res=>{
353
+ localStorage.setItem('token', res.token);
354
+ })
355
+ ```
356
+
357
+ ### API
358
+
359
+ #### SoonOptions
360
+
361
+ ```ts
362
+ // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
363
+ // RequestInit 为原生 fetch 的 init 选项
364
+ type SoonOptions = Omit<RequestInit, "body"> & {
365
+ query?:
366
+ | Record<
367
+ string,
368
+ | string
369
+ | number
370
+ | boolean
371
+ | null
372
+ | undefined
373
+ | (string | number | boolean | null | undefined)[]
374
+ >
375
+ | URLSearchParams;
376
+ params?: Record<string, string | number>;
377
+ timeout?: number;
378
+ body?: RequestInit["body"] | object;
379
+ };
380
+ ```
381
+
382
+ [English](#soon-fetch) | [中文](#soon-fetch-1) | [Installation](#安装-installation)
383
+
384
+ <!-- omit in toc -->
385
+
386
+ ##### 安装 Installation
387
+
388
+ ```bash
389
+ npm install soon-fetch
390
+ ```
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 u=s.trim();e(s).forEach(e=>{c&&(u=u.replace(":"+e,""+c[e]))});const[p,h]=u.split("?"),y=new URLSearchParams([...o(h),...o(i)]);let l=((e,o)=>{if(n(e))return e;const s=n(o)?o:r(o);return t(s)+t(r(e))})(p,f);return y.size&&(l=l+"?"+y),l},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}const f=["get","post","put","delete","patch"];function u(e,t){const r={};return e.forEach(e=>{r[e]=(r,n)=>t(r,{...n,method:e})}),r}function p(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,u=c?a.shift():void 0,p=i?a.shift():void 0,h=a.shift();return t(r,n,f,u,p,h,o?.options)}},n={};return f.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 h(e){return p((t,r,n,o,s,a,i)=>e(t,{...a,method:r,params:n,query:o,body:s}))}function y(e){if(Array.isArray(e))return e.map(y);if("object"==typeof e&&null!==e){const t={};return Object.keys(e).sort().forEach(r=>{t[r]=y(e[r])}),t}return e}exports.createCache=function(){const e={};let t=[];function r(r){delete e[r],t=t.filter(e=>e.key!==r)}return 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),{get:function(t){const n=e[t];if(void 0!==n)return n.expiredTime>Date.now()?n.data:void r(t)},set:function(r,n,o){e[r]={data:n,expiredTime:o},t.push({key:r,expiredTime:o})},remove:r}},exports.createShare=function(){const e={},t=t=>e[t]=void 0;return{get:t=>e[t],set:(r,n)=>{e[r]=n,n.finally(()=>t(r))}}},exports.createShortAPI=h,exports.createShortApiGeneral=p,exports.createShortMethods=u,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){const t=e;return{request:t,...h(e),...u([...f,"head","options"],t)}},exports.deepSort=y,exports.genRequestKey=function(e){const{url:t,headers:r,method:n,body:o}=e,s=y(Object.fromEntries(r?.entries()??[]));return(n??"get").toLowerCase()+t+JSON.stringify(s)+("object"==typeof o&&null!=o?JSON.stringify(y(o)):o)},exports.isBodyJson=c,exports.mergeHeaders=a,exports.mergeSignals=i,exports.mergeUrl=s,exports.parseUrlOptions=function(e){const{url:t,options:r,baseURL:n,baseOptions:o}=e,f=function(e,t){const r={...e,...t},n=a(e?.headers,t?.headers);return r.headers=n,r.signal=i([e?.signal,t?.signal],r.timeout),r}(o,r),u=s(t,{...f,baseURL:n}),p=f?.body,h=c(p);return f.body=h?JSON.stringify(p):p,h&&f.headers.append("Content-Type","application/json"),f.signal=i([f.signal]),[u,f]},exports.raceAbort=function(e,t){t&&(t.pop()?.abort(),t.push(e))};
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 u=s.trim();e(s).forEach((e=>{c&&(u=u.replace(":"+e,""+c[e]))}));const[p,h]=u.split("?"),l=new URLSearchParams([...o(h),...o(i)]);let y=((e,o)=>{if(n(e))return e;const s=n(o)?o:r(o);return t(s)+t(r(e))})(p,f);return l.size&&(y=y+"?"+l),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}const f=["get","post","put","delete","patch"];function u(e,t){const r={};return e.forEach((e=>{r[e]=t(e)})),r}function p(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,u=c?a.shift():void 0,p=i?a.shift():void 0,h=a.shift();return t(r,n,f,u,p,h,o?.options)}},n={};return f.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 h(e,t){t&&(t.pop()?.abort(),t.push(e))}function l(e){if(Array.isArray(e))return e.map(l);if("object"==typeof e&&null!==e){const t={};return Object.keys(e).sort().forEach((r=>{t[r]=l(e[r])})),t}return e}function y(e){const{url:t,headers:r,method:n,body:o,query:s,params:a}=e,i=l(Object.fromEntries(r?.entries()??[]));return(n??"get").toLowerCase()+t+JSON.stringify(l(s)??"")+JSON.stringify(l(a)??"")+JSON.stringify(i)+("object"==typeof o&&null!=o?JSON.stringify(l(o)):o)}function d(){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 g(){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=d,exports.createShare=g,exports.createShortApi=p,exports.createShortMethods=u,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){const t=d(),r=g(),n=(n,o)=>new Promise(((s,a)=>{const i=new AbortController,c=e({abortController:i});h(i,o?.aborts);const f=y({url:n,...o,headers:new Headers(o?.headers)});if(o?.share){const e=r.get(f);if(e)return s(e)}if(o?.staleTime){const e=t.get(f);if(void 0!==e)return s(e)}const u=c(n,o);o?.share&&r.set(f,u),u.then((e=>{s(e),o?.staleTime&&t.set(f,e,(new Date).getTime()+o.staleTime)})).catch((e=>a(e)))})),o=p(((e,t,r,o,s,a,i)=>n(e,{...i,...a,method:t,params:r,query:o,body:s}))),s=u([...f,"head","options"],(e=>(t,r)=>n(t,{...r,method:e})));return{request:n,...o,...s}},exports.deepSort=l,exports.genRequestKey=y,exports.isBodyJson=c,exports.mergeHeaders=a,exports.mergeSignals=i,exports.mergeUrl=s,exports.parseUrlOptions=function(e){const{url:t,options:r,baseURL:n,baseOptions:o}=e,f=function(e,t){const r={...e,...t},n=a(e?.headers,t?.headers);return r.headers=n,r.signal=i([e?.signal,t?.signal],r.timeout),r}(o,r),u=s(t,{...f,baseURL:n}),p=f?.body,h=c(p);return f.body=h?JSON.stringify(p):p,h&&f.headers.append("Content-Type","application/json"),f.signal=i([f.signal]),[u,f,h]},exports.raceAbort=h;
package/dist/index.d.ts CHANGED
@@ -3,12 +3,15 @@ type SoonOptions = Omit<RequestInit, "body"> & {
3
3
  params?: Record<string, string | number>;
4
4
  timeout?: number;
5
5
  body?: RequestInit["body"] | object;
6
+ aborts?: AbortController[];
7
+ share?: boolean;
8
+ staleTime?: number;
6
9
  };
7
10
  type GetUrlKey<Url> = Url extends `${string}/:${infer Key}/${infer Right}` ? `${Key}` | GetUrlKey<`/${Right}`> : Url extends `${string}/:${infer Key}` ? `${Key}` : never;
8
11
  type OptionParams<Args> = NonNullable<Args> extends never ? [] : keyof NonNullable<Args> extends never ? [] : Exclude<Args, NonNullable<Args>> extends never ? [params: Args] : [params?: Args];
9
12
  type OptionQuery<Args> = NonNullable<Args> extends never ? [] : keyof NonNullable<Args> extends never ? [] : Exclude<Args, NonNullable<Args>> extends never ? Partial<Args> extends Args ? [query?: Args] : [query: Args] : [query?: Args];
10
13
  type OptionBody<Args> = NonNullable<Args> extends never ? [] : Exclude<Args, NonNullable<Args>> extends never ? [body: Args] : [body?: Args];
11
- type Tuple2Union<T> = T extends [infer T1, infer T2, ...infer R] ? T1 | T2 | Tuple2Union<R> : T extends [infer T_Only] ? T_Only : never;
14
+ type Tuple2Union<T> = T extends readonly [infer T1, infer T2, ...infer R] ? T1 | T2 | Tuple2Union<R> : T extends [infer T_Only] ? T_Only : never;
12
15
 
13
16
  declare const mergeUrl: (url: string, config: {
14
17
  query?: Record<string, string | number | boolean | null | undefined | (string | number | boolean | null | undefined)[]> | URLSearchParams;
@@ -26,130 +29,113 @@ declare function parseUrlOptions<Options extends SoonOptions>(urlOptions: {
26
29
  }): readonly [string, Options & {
27
30
  headers: Headers;
28
31
  body?: BodyInit | null;
29
- }];
30
- declare function createSoon<RequestFn extends <R>(url: string, options?: SoonOptions) => Promise<any>>(requestFn: RequestFn): {
31
- options: RequestFn;
32
- get: RequestFn;
33
- post: RequestFn;
34
- put: RequestFn;
35
- delete: RequestFn;
36
- patch: RequestFn;
37
- head: RequestFn;
32
+ }, boolean];
33
+ declare function createShortMethods<Methods extends readonly string[], Wrapper extends (method: string) => <T>(...args: any) => Promise<T>>(methods: Methods, wrapper: Wrapper): Record<Tuple2Union<Methods>, ReturnType<typeof wrapper>>;
34
+ declare function createShortApi<Wrapper extends <T>(url: string, method: string, params: Record<string, string | number> | undefined, query: Record<string, string | number | boolean | null | undefined | (string | number | boolean | null | undefined)[]> | URLSearchParams | undefined, body: object | undefined, options?: any, defineOptions?: any) => Promise<T>>(wrapper: Wrapper): {
38
35
  GET: <Url extends string>(url: Url) => {
39
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
36
+ Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
37
+ Query: <Query>() => unknown extends Query ? never : {
38
+ Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
39
+ };
40
+ };
41
+ } & Record<"POST" | "PATCH" | "DELETE" | "PUT", <Url extends string>(url: Url) => {
42
+ Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
43
+ Body: <Body>() => unknown extends Body ? never : {
44
+ Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
45
+ };
46
+ Query: <Query>() => unknown extends Query ? never : {
47
+ Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
48
+ Body: <Body>() => unknown extends Body ? never : {
49
+ 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>;
50
+ };
51
+ };
52
+ }>;
53
+ type NoData<T> = Omit<T, "method" | "body" | "params" | "query">;
54
+ declare function raceAbort(abortController: AbortController, controllers?: AbortController[]): void;
55
+ declare function deepSort(obj: unknown): unknown;
56
+ declare function genRequestKey(req: {
57
+ url: string;
58
+ method?: string;
59
+ headers?: Headers;
60
+ body?: RequestInit["body"] | object;
61
+ query?: Record<string, string | number | boolean | null | undefined | (string | number | boolean | null | undefined)[]> | URLSearchParams;
62
+ params?: Record<string, string | number>;
63
+ }): string;
64
+ declare function createCache(): {
65
+ get: (key: string) => unknown;
66
+ set: (key: string, res: Response | unknown, expiredTime: number) => void;
67
+ remove: (key: string) => void;
68
+ };
69
+ declare function createShare(): {
70
+ get: (key: string) => Promise<any> | undefined;
71
+ set: (key: string, value: Promise<any>) => void;
72
+ };
73
+ declare function createSilentRefresh(refresh_token_fn: () => Promise<void>): (success: () => void, fail: () => void) => void;
74
+ declare function createSoon<Options extends SoonOptions>(wrapper: (instance: {
75
+ abortController: AbortController;
76
+ }) => <T>(url: string, options?: Options) => Promise<T>): {
77
+ options: <T>(url: string, options?: Options) => Promise<T>;
78
+ get: <T>(url: string, options?: Options) => Promise<T>;
79
+ post: <T>(url: string, options?: Options) => Promise<T>;
80
+ put: <T>(url: string, options?: Options) => Promise<T>;
81
+ delete: <T>(url: string, options?: Options) => Promise<T>;
82
+ patch: <T>(url: string, options?: Options) => Promise<T>;
83
+ head: <T>(url: string, options?: Options) => Promise<T>;
84
+ GET: <Url extends string>(url: Url) => {
85
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: NoData<Options> | undefined]) => Promise<Res>;
40
86
  Query: <Query>() => unknown extends Query ? never : {
41
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
87
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: NoData<Options> | undefined]) => Promise<Res>;
42
88
  };
43
89
  };
44
90
  POST: <Url extends string>(url: Url) => {
45
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
91
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: NoData<Options> | undefined]) => Promise<Res>;
46
92
  Body: <Body>() => unknown extends Body ? never : {
47
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
93
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
48
94
  };
49
95
  Query: <Query>() => unknown extends Query ? never : {
50
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
96
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: NoData<Options> | undefined]) => Promise<Res>;
51
97
  Body: <Body>() => unknown extends Body ? never : {
52
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
98
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
53
99
  };
54
100
  };
55
101
  };
56
102
  PATCH: <Url extends string>(url: Url) => {
57
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
103
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: NoData<Options> | undefined]) => Promise<Res>;
58
104
  Body: <Body>() => unknown extends Body ? never : {
59
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
105
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
60
106
  };
61
107
  Query: <Query>() => unknown extends Query ? never : {
62
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
108
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: NoData<Options> | undefined]) => Promise<Res>;
63
109
  Body: <Body>() => unknown extends Body ? never : {
64
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
110
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
65
111
  };
66
112
  };
67
113
  };
68
114
  DELETE: <Url extends string>(url: Url) => {
69
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
115
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: NoData<Options> | undefined]) => Promise<Res>;
70
116
  Body: <Body>() => unknown extends Body ? never : {
71
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
117
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
72
118
  };
73
119
  Query: <Query>() => unknown extends Query ? never : {
74
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
120
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: NoData<Options> | undefined]) => Promise<Res>;
75
121
  Body: <Body>() => unknown extends Body ? never : {
76
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
122
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
77
123
  };
78
124
  };
79
125
  };
80
126
  PUT: <Url extends string>(url: Url) => {
81
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
127
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: NoData<Options> | undefined]) => Promise<Res>;
82
128
  Body: <Body>() => unknown extends Body ? never : {
83
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
129
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
84
130
  };
85
131
  Query: <Query>() => unknown extends Query ? never : {
86
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
132
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: NoData<Options> | undefined]) => Promise<Res>;
87
133
  Body: <Body>() => unknown extends Body ? never : {
88
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: Omit<Exclude<Parameters<NoInfer<RequestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
134
+ Send: <Res>(options?: NoData<Options> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: NoData<Options> | undefined]) => Promise<Res>;
89
135
  };
90
136
  };
91
137
  };
92
- request: RequestFn;
93
- };
94
- declare function createShortMethods<Methods extends string[], RequestFun extends <T>(url: string, options?: {
95
- method?: string;
96
- }) => Promise<any>>(methods: Methods, requestFun: RequestFun): Record<Tuple2Union<Methods>, typeof requestFun>;
97
- declare function createShortApiGeneral<Wrapper extends <T>(url: string, method: string, params: Record<string, string | number> | undefined, query: Record<string, string | number | boolean | null | undefined | (string | number | boolean | null | undefined)[]> | URLSearchParams | undefined, body: object | undefined, options?: any, defineOptions?: any) => Promise<T>>(wrapper: Wrapper): {
98
- GET: <Url extends string>(url: Url) => {
99
- Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
100
- Query: <Query>() => unknown extends Query ? never : {
101
- Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
102
- };
103
- };
104
- } & Record<"POST" | "PATCH" | "DELETE" | "PUT", <Url extends string>(url: Url) => {
105
- Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
106
- Body: <Body>() => unknown extends Body ? never : {
107
- Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
108
- };
109
- Query: <Query>() => unknown extends Query ? never : {
110
- Send: <Res>(options?: Parameters<Wrapper>[5]) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Parameters<Wrapper>[5] | undefined]) => Promise<Res>;
111
- Body: <Body>() => unknown extends Body ? never : {
112
- 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>;
113
- };
114
- };
115
- }>;
116
- declare function createShortAPI<requestFn extends <T>(url: string, options?: SoonOptions) => Promise<T>>(requestFn: requestFn): {
117
- GET: <Url extends string>(url: Url) => {
118
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
119
- Query: <Query>() => unknown extends Query ? never : {
120
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
121
- };
122
- };
123
- } & Record<"POST" | "PATCH" | "DELETE" | "PUT", <Url extends string>(url: Url) => {
124
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
125
- Body: <Body>() => unknown extends Body ? never : {
126
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionBody<Body>, options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
127
- };
128
- Query: <Query>() => unknown extends Query ? never : {
129
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
130
- Body: <Body>() => unknown extends Body ? never : {
131
- Send: <Res>(options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined) => (...arg: [...OptionParams<{ [key in GetUrlKey<Url>]: string | number; }>, ...OptionQuery<Query>, ...OptionBody<Body>, options?: Omit<Exclude<Parameters<NoInfer<requestFn>>[1], undefined>, "body" | "method" | "params" | "query"> | undefined]) => Promise<Res>;
132
- };
133
- };
134
- }>;
135
- declare function raceAbort(abortController: AbortController, controllers?: AbortController[]): void;
136
- declare function deepSort(obj: unknown): unknown;
137
- declare function genRequestKey(req: {
138
- url: string;
139
- headers?: Headers;
140
- method?: string;
141
- body?: any;
142
- }): string;
143
- declare function createCache(): {
144
- get: (key: string) => unknown;
145
- set: (key: string, res: unknown, expiredTime: number) => void;
146
- remove: (key: string) => void;
138
+ request: <T>(url: string, options: Options) => Promise<T>;
147
139
  };
148
- declare function createShare(): {
149
- get: (key: string) => Promise<any> | undefined;
150
- set: (key: string, value: Promise<any>) => void;
151
- };
152
- declare function createSilentRefresh(refresh_token_fn: () => Promise<void>): (success: () => void, fail: () => void) => void;
153
140
 
154
- export { createCache, createShare, createShortAPI, createShortApiGeneral, createShortMethods, createSilentRefresh, createSoon, deepSort, genRequestKey, isBodyJson, mergeHeaders, mergeSignals, mergeUrl, parseUrlOptions, raceAbort };
155
- export type { SoonOptions };
141
+ export { type SoonOptions, createCache, createShare, createShortApi, createShortMethods, createSilentRefresh, createSoon, deepSort, genRequestKey, isBodyJson, mergeHeaders, mergeSignals, mergeUrl, parseUrlOptions, raceAbort };
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},a=(a,s)=>{const{query:i,params:c,baseURL:f}=s;let u=a.trim();t(a).forEach(t=>{c&&(u=u.replace(":"+t,""+c[t]))});const[h,y]=u.split("?"),l=new URLSearchParams([...o(y),...o(i)]);let d=((t,o)=>{if(r(t))return t;const a=r(o)?o:n(o);return e(a)+e(n(t))})(h,f);return l.size&&(d=d+"?"+l),d},s=(...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{url:e,options:n,baseURL:r,baseOptions:o}=t,f=function(t,e){const n={...t,...e},r=s(t?.headers,e?.headers);return n.headers=r,n.signal=i([t?.signal,e?.signal],n.timeout),n}(o,n),u=a(e,{...f,baseURL:r}),h=f?.body,y=c(h);return f.body=y?JSON.stringify(h):h,y&&f.headers.append("Content-Type","application/json"),f.signal=i([f.signal]),[u,f]}const u=["get","post","put","delete","patch"];function h(t){const e=t;return{request:e,...d(t),...y([...u,"head","options"],e)}}function y(t,e){const n={};return t.forEach(t=>{n[t]=(n,r)=>e(n,{...r,method:t})}),n}function l(e){const n=(n,r,o)=>{const a=!!t(n).length;return(...t)=>{const s=[...t],{hasBody:i,hasQuery:c}=o||{},f=a?s.shift():void 0,u=c?s.shift():void 0,h=i?s.shift():void 0,y=s.shift();return e(n,r,f,u,h,y,o?.options)}},r={};return u.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){return l((e,n,r,o,a,s,i)=>t(e,{...s,method:n,params:r,query:o,body:a}))}function p(t,e){e&&(e.pop()?.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,headers:n,method:r,body:o}=t,a=m(Object.fromEntries(n?.entries()??[]));return(r??"get").toLowerCase()+e+JSON.stringify(a)+("object"==typeof o&&null!=o?JSON.stringify(m(o)):o)}function A(){const t={};let e=[];function n(n){delete t[n],e=e.filter(t=>t.key!==n)}return 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),{get:function(e){const r=t[e];if(void 0!==r)return r.expiredTime>Date.now()?r.data:void n(e)},set:function(n,r,o){t[n]={data:r,expiredTime:o},e.push({key:n,expiredTime:o})},remove:n}}function b(){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=[]}))}}export{A as createCache,b as createShare,d as createShortAPI,l as createShortApiGeneral,y as createShortMethods,S as createSilentRefresh,h as createSoon,m as deepSort,g as genRequestKey,c as isBodyJson,s as mergeHeaders,i as mergeSignals,a as mergeUrl,f as parseUrlOptions,p as raceAbort};
1
+ const e=e=>{const t=[],n=e.match(/:([^:/\d]+)\/?/g);return n&&n.forEach((e=>{t.push(e.replace(/\//g,"").replace(/:/g,""))})),t},t=(e="")=>e.endsWith("/")?e.slice(0,-1):e,n=(e="")=>e.startsWith("/")?e:"/"+e,r=(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((n=>{const r=e[n];(Array.isArray(r)?r:[r]).forEach((e=>{t.push([n,e??""])}))})),t},s=(s,a)=>{const{query:i,params:c,baseURL:f}=a;let u=s.trim();e(s).forEach((e=>{c&&(u=u.replace(":"+e,""+c[e]))}));const[h,l]=u.split("?"),y=new URLSearchParams([...o(l),...o(i)]);let d=((e,o)=>{if(r(e))return e;const s=r(o)?o:n(o);return t(s)+t(n(e))})(h,f);return y.size&&(d=d+"?"+y),d},a=(...e)=>{const t=new Headers;return e.forEach((e=>{e&&new Headers(e).forEach(((e,n)=>{t.set(n,e)}))})),t};function i(e,t){const n=(e??[]).filter((e=>!!e));return t&&n.push(AbortSignal.timeout(t)),n.length?AbortSignal.any(n):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{url:t,options:n,baseURL:r,baseOptions:o}=e,f=function(e,t){const n={...e,...t},r=a(e?.headers,t?.headers);return n.headers=r,n.signal=i([e?.signal,t?.signal],n.timeout),n}(o,n),u=s(t,{...f,baseURL:r}),h=f?.body,l=c(h);return f.body=l?JSON.stringify(h):h,l&&f.headers.append("Content-Type","application/json"),f.signal=i([f.signal]),[u,f,l]}const u=["get","post","put","delete","patch"];function h(e,t){const n={};return e.forEach((e=>{n[e]=t(e)})),n}function l(t){const n=(n,r,o)=>{const s=!!e(n).length;return(...e)=>{const a=[...e],{hasBody:i,hasQuery:c}=o||{},f=s?a.shift():void 0,u=c?a.shift():void 0,h=i?a.shift():void 0,l=a.shift();return t(n,r,f,u,h,l,o?.options)}},r={};return u.forEach((e=>{const t=e.toUpperCase();r[t]=t=>({Send:r=>n(t,e,{options:r}),Body:()=>({Send:r=>n(t,e,{hasBody:!0,options:r})}),Query:()=>({Send:r=>n(t,e,{hasQuery:!0,options:r}),Body:()=>({Send:r=>n(t,e,{hasBody:!0,hasQuery:!0,options:r})})})})})),r}function y(e,t){t&&(t.pop()?.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((n=>{t[n]=d(e[n])})),t}return e}function p(e){const{url:t,headers:n,method:r,body:o,query:s,params:a}=e,i=d(Object.fromEntries(n?.entries()??[]));return(r??"get").toLowerCase()+t+JSON.stringify(d(s)??"")+JSON.stringify(d(a)??"")+JSON.stringify(i)+("object"==typeof o&&null!=o?JSON.stringify(d(o)):o)}function m(){const e={},t=[];setInterval((()=>{const n=Date.now();for(let r=t.length-1;r>=0;r--)t[r].expiredTime<n&&(delete e[t[r].key],t.splice(r,1))}),6e4);const n=e=>e instanceof Response?e.clone():"function"==typeof e||e instanceof Promise?e:structuredClone(e);function r(n){delete e[n];for(let e=t.length-1;e>=0;e--)if(t[e].key===n){t.splice(e,1);break}}return{get:function(t){const o=e[t];if(void 0!==o)return o.expiredTime>Date.now()?n(o.data):void r(t)},set:function(r,o,s){e[r]={data:n(o),expiredTime:s},t.push({key:r,expiredTime:s})},remove:r}}function g(){const e={},t=t=>e[t]=void 0;return{get:t=>e[t],set:(n,r)=>{e[n]=r,r.finally((()=>t(n)))}}}function b(e){let t=[],n=!1;return(r,o)=>{t.push({success:r,fail:o}),n||(n=!0,e().then((()=>{t.forEach((e=>e.success()))})).catch((e=>{t.forEach((e=>e.fail()))})).finally((()=>{n=!1,t=[]})))}}function A(e){const t=m(),n=g(),r=(r,o)=>new Promise(((s,a)=>{const i=new AbortController,c=e({abortController:i});y(i,o?.aborts);const f=p({url:r,...o,headers:new Headers(o?.headers)});if(o?.share){const e=n.get(f);if(e)return s(e)}if(o?.staleTime){const e=t.get(f);if(void 0!==e)return s(e)}const u=c(r,o);o?.share&&n.set(f,u),u.then((e=>{s(e),o?.staleTime&&t.set(f,e,(new Date).getTime()+o.staleTime)})).catch((e=>a(e)))})),o=l(((e,t,n,o,s,a,i)=>r(e,{...i,...a,method:t,params:n,query:o,body:s}))),s=h([...u,"head","options"],(e=>(t,n)=>r(t,{...n,method:e})));return{request:r,...o,...s}}export{m as createCache,g as createShare,l as createShortApi,h as createShortMethods,b as createSilentRefresh,A as createSoon,d as deepSort,p as genRequestKey,c as isBodyJson,a as mergeHeaders,i as mergeSignals,s as mergeUrl,f as parseUrlOptions,y as raceAbort};
package/package.json CHANGED
@@ -1,41 +1,41 @@
1
- {
2
- "name": "soon-fetch",
3
- "version": "3.0.0-beta.1",
4
- "description": "a 3Kb request lib alternative to axios",
5
- "homepage": "https://github.com/leafio/soon-fetch",
6
- "main": "./dist/index.cjs.js",
7
- "module": "/dist/index.js",
8
- "types": "./dist/index.d.ts",
9
- "files": [
10
- "./dist/*"
11
- ],
12
- "scripts": {
13
- "build:types": "tsc",
14
- "build": "rollup --config",
15
- "test": "echo \"Error: no test specified\" && exit 1"
16
- },
17
- "keywords": [
18
- "request",
19
- "fetch",
20
- "lightweight",
21
- "timeout",
22
- "http",
23
- "axios"
24
- ],
25
- "author": "",
26
- "license": "MIT",
27
- "devDependencies": {
28
- "@babel/core": "^7.25.2",
29
- "@babel/preset-env": "^7.25.4",
30
- "@rollup/plugin-babel": "^6.0.3",
31
- "@rollup/plugin-node-resolve": "^15.1.0",
32
- "@rollup/plugin-terser": "^0.4.3",
33
- "rollup": "^4.22.0",
34
- "rollup-plugin-commonjs": "^10.1.0",
35
- "rollup-plugin-dts": "^6.1.1",
36
- "rollup-plugin-typescript": "^1.0.1",
37
- "tslib": "^2.7.0",
38
- "typescript": "^5.6.2"
39
- },
40
- "dependencies": {}
41
- }
1
+ {
2
+ "name": "soon-fetch",
3
+ "version": "3.0.0-beta.2",
4
+ "description": "a 3Kb request lib alternative to axios",
5
+ "homepage": "https://github.com/leafio/soon-fetch",
6
+ "main": "./dist/index.cjs.js",
7
+ "module": "/dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "files": [
10
+ "./dist/*"
11
+ ],
12
+ "scripts": {
13
+ "build:types": "tsc",
14
+ "build": "rollup --config",
15
+ "test": "echo \"Error: no test specified\" && exit 1"
16
+ },
17
+ "keywords": [
18
+ "request",
19
+ "fetch",
20
+ "lightweight",
21
+ "timeout",
22
+ "http",
23
+ "axios"
24
+ ],
25
+ "author": "",
26
+ "license": "MIT",
27
+ "devDependencies": {
28
+ "@babel/core": "^7.25.2",
29
+ "@babel/preset-env": "^7.25.4",
30
+ "@rollup/plugin-babel": "^6.0.3",
31
+ "@rollup/plugin-node-resolve": "^15.1.0",
32
+ "@rollup/plugin-terser": "^0.4.3",
33
+ "rollup": "^4.22.0",
34
+ "rollup-plugin-commonjs": "^10.1.0",
35
+ "rollup-plugin-dts": "^6.1.1",
36
+ "rollup-plugin-typescript": "^1.0.1",
37
+ "tslib": "^2.7.0",
38
+ "typescript": "^5.6.2"
39
+ },
40
+ "dependencies": {}
41
+ }