soon-fetch 3.0.0-beta.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,13 +4,16 @@
4
4
 
5
5
  ### soon-fetch
6
6
 
7
- **A lightweight http request lib , alternative to axios**
7
+ **A lightweight http request lib , alternative to axios with timeout, request reusing, race, response cache ...**
8
8
 
9
9
  > - 🌐 automatic parse restful api url parameters
10
10
  > - ⭐ rapid define a request api
11
11
  > - ⌛ timeout disconnect
12
- > - 🔤 automatic parse or serialization of JSON
13
- > - 📏 .min size less than **3K**, smaller after zip
12
+ > - 📦 request reusing
13
+ > - 🚀 request race
14
+ > - 📝 response cache
15
+ > - 🔤 automatic serialization of JSON
16
+ > - 📏 .min size less than **5K**, smaller after zip
14
17
  > - 💡 smart type tips with Typescript
15
18
 
16
19
  - [Example](#example)
@@ -19,7 +22,7 @@
19
22
  - [Restful Url Params](#restful-url-params)
20
23
  - [Timeout](#timeout)
21
24
  - [Share pending request](#share-pending-request)
22
- - [Cache response](#cache-response)
25
+ - [Response cache](#response-cache)
23
26
  - [Request race](#request-race)
24
27
  - [Rapid Define APIs](#rapid-define-apis)
25
28
  - [API](#api)
@@ -63,9 +66,10 @@ soon.post("/login", { body: { username: "admin", password: "123456" } });
63
66
 
64
67
  /**Define API */
65
68
  export const login = soon
66
- .API("/user/login")
67
- .POST<{ username: string; password: string }, { token: string }>();
68
-
69
+ .POST("/user/login")
70
+ .Body<{ username: string; password: string }>()
71
+ .Send<{ token: string }>();
72
+ //the develop tools will have type tips for request and response
69
73
  login({ username: "admin", password: "123" }).then((res) => {
70
74
  localStorage.setItem("token", res.token);
71
75
  });
@@ -111,7 +115,7 @@ If a request is made again before the first completes, will reuse the first requ
111
115
  soon.get(url, { share: true });
112
116
  ```
113
117
 
114
- ##### Cache response
118
+ ##### Response cache
115
119
 
116
120
  A cached response will be returned if the request is made again within the specified time.
117
121
 
@@ -124,14 +128,18 @@ soon.get(url, { staleTime: 1000 * 60 * 5 });
124
128
  ‌If a second request is made before the first completes, abort the first to avoid race conditions from out-of-order responses.
125
129
 
126
130
  ```tsx
131
+ import { useEffect, useRef, useState } from "react";
132
+
127
133
  type User = { name: string; job: string };
128
134
  const api = soon.GET("/api/users").Query<{ page: number }>().Send<User[]>();
129
135
  export default function App() {
130
- const refAbort = useRef<AbortController[]>([]);
136
+ const refAbort = useRef([]);
131
137
  const [list, setList] = useState<User[]>([]);
132
138
  const [page, setPage] = useState(1);
133
139
  useEffect(() => {
134
- api({ page }).then(setList).catch(console.log);
140
+ api({ page }, { aborts: refAbort.current })
141
+ .then(setList)
142
+ .catch(console.log);
135
143
  }, [page]);
136
144
  return (
137
145
  <div>
@@ -150,7 +158,8 @@ export default function App() {
150
158
 
151
159
  ```typescript
152
160
  //can be GET POST PATCH PUT DELETE
153
-
161
+ soon.GET(url:string).Query<Query>().Send<Response>()
162
+ soon.POST(url:string).Body<Body>().Send<Response>()
154
163
  //define an api
155
164
  export const getUserInfo = soon.GET("/user/:id").Send();
156
165
  //then use in any where
@@ -175,6 +184,7 @@ login({ username: "admin", password: "123" }).then((res) => {
175
184
  // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
176
185
  // RequestInit is fetch's init options
177
186
  type SoonOptions = Omit<RequestInit, "body"> & {
187
+ body?: RequestInit["body"] | object;
178
188
  query?:
179
189
  | Record<
180
190
  string,
@@ -188,8 +198,7 @@ type SoonOptions = Omit<RequestInit, "body"> & {
188
198
  | URLSearchParams;
189
199
  params?: Record<string, string | number>;
190
200
  timeout?: number;
191
- body?: RequestInit["body"] | object;
192
- aborts?: AbortController[];
201
+ aborts?: AbortController[] | never[];
193
202
  share?: boolean;
194
203
  staleTime?: number;
195
204
  };
@@ -206,8 +215,11 @@ type SoonOptions = Omit<RequestInit, "body"> & {
206
215
  > - 🌐 自动解析 rest Url 的参数
207
216
  > - ⭐ 快捷定义请求 api
208
217
  > - ⌛ 超时断开
218
+ > - 📦 请求复用
219
+ > - 🚀 请求竞态
220
+ > - 📝 响应缓存
209
221
  > - 🔤 自动处理 JSON
210
- > - 📏 不到 **3K** , zip 后会更小
222
+ > - 📏 不到 **5K** , zip 后会更小
211
223
  > - 💡 用 typescript 有智能类型提醒
212
224
 
213
225
  - [示例](#示例)
@@ -218,7 +230,8 @@ type SoonOptions = Omit<RequestInit, "body"> & {
218
230
  - [Restful Url 参数自动处理](#restful-url-参数自动处理)
219
231
  - [共享未完成的请求](#共享未完成的请求)
220
232
  - [超时](#超时)
221
- - [缓存](#缓存) -[请求竞态](#请求竞态)
233
+ - [响应缓存](#响应缓存)
234
+ - [请求竞态](#请求竞态)
222
235
  - [快速定义 API](#快速定义-api)
223
236
 
224
237
  - [API](#api-1)
@@ -260,9 +273,11 @@ soon.post("/login", { body: { username: "admin", password: "123456" } });
260
273
 
261
274
  /**定义 API */
262
275
  export const login = soon
263
- .API("/user/login")
264
- .POST<{ username: string; password: string }, { token: string }>();
276
+ .POST("/user/login")
277
+ .Body<{ username: string; password: string }>()
278
+ .Send<{ token: string }>();
265
279
 
280
+ //开发工具会有请求和响应的智能提醒
266
281
  login({ username: "admin", password: "123" }).then((res) => {
267
282
  localStorage.setItem("token", res.token);
268
283
  });
@@ -308,7 +323,7 @@ soon.get(url, { timeout: 1000 * 20 });
308
323
  soon.get(url, { share: true });
309
324
  ```
310
325
 
311
- ##### 缓存
326
+ ##### 响应缓存
312
327
 
313
328
  如果在指定时间内再次发起相同的请求,则会返回缓存的响应。
314
329
 
@@ -321,10 +336,12 @@ soon.get(url, { staleTime: 1000 * 60 * 5 });
321
336
  如果在第一个请求完成之前发起第二个请求,则会中止第一个请求,以避免因响应顺序错乱导致的问题。
322
337
 
323
338
  ```tsx
339
+ import { useEffect, useRef, useState } from "react";
340
+
324
341
  type User = { name: string; job: string };
325
342
  const api = soon.GET("/api/users").Query<{ page: number }>().Send<User[]>();
326
343
  export default function App() {
327
- const refAbort = useRef<AbortController[]>([]);
344
+ const refAbort = useRef([]);
328
345
  const [list, setList] = useState<User[]>([]);
329
346
  const [page, setPage] = useState(1);
330
347
  useEffect(() => {
@@ -347,19 +364,22 @@ export default function App() {
347
364
 
348
365
  ##### 快速定义 API
349
366
 
350
- ```typescript
367
+ ```ts
351
368
  //可以是 GET POST PATCH PUT DELETE
352
369
  //GET 请求数据传递至query,其他方法请求数据传递至body
353
- soon.API(url:string).POST<RequestType,ResponseType>()
370
+ soon.GET(url:string).Query<Query>().Send<Response>()
371
+ soon.POST(url:string).Body<Body>().Send<Response>()
354
372
 
355
373
  //定义一个api
356
- export const getUserInfo=soon.API('/user/:id').GET()
374
+ export const getUserInfo=soon.GET('/user/:id').Send()
357
375
  //使用
358
376
  getUserInfo({id:2}).then(res=>console.log(res))
359
377
 
360
378
  //用typescript,
361
- export const login=soon.API('/user/login')
362
- .POST<{username:string,password:string},{token:string}>()
379
+ export const login=soon
380
+ .POST('/user/login')
381
+ .Body<{username:string,password:string}>()
382
+ .Send<{token:string}>()
363
383
  //开发工具会有请求和响应的智能提醒
364
384
  login({username:'admin',password:'123'}).then(res=>{
365
385
  localStorage.setItem('token', res.token);
@@ -374,6 +394,7 @@ export default function App() {
374
394
  // function fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>
375
395
  // RequestInit 为原生 fetch 的 init 选项
376
396
  type SoonOptions = Omit<RequestInit, "body"> & {
397
+ body?: RequestInit["body"] | object;
377
398
  query?:
378
399
  | Record<
379
400
  string,
@@ -387,8 +408,7 @@ type SoonOptions = Omit<RequestInit, "body"> & {
387
408
  | URLSearchParams;
388
409
  params?: Record<string, string | number>;
389
410
  timeout?: number;
390
- body?: RequestInit["body"] | object;
391
- aborts?: AbortController[];
411
+ aborts?: AbortController[] | never[];
392
412
  share?: boolean;
393
413
  staleTime?: number;
394
414
  };
package/dist/index.cjs.js CHANGED
@@ -1 +1 @@
1
- "use strict";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 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(r(e))return e;const s=r(o)?o:n(o);return t(s)+t(n(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,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 t=Object.assign({},...e),n=a(...e.map((e=>e?.headers)));return t.headers=n,t.signal=i(e.map((e=>e?.signal)),t.timeout),t}function p(e){const{url:t,options:n,baseURL:r,baseOptions:o}=e,a=f(o,n),p=s(t,{...a,baseURL:r}),u=a?.body,l=c(u);return a.body=l?JSON.stringify(u):u,l&&a.headers.append("Content-Type","application/json"),a.signal=i([a.signal]),[p,a]}const u=["get","post","put","delete","patch"];function l(e,t){const n={};return e.forEach((e=>{n[e]=t(e)})),n}function h(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,p=c?a.shift():void 0,u=i?a.shift():void 0,l=a.shift();return t(n,r,f,p,u,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("soon-fetch 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((n=>{t[n]=d(e[n])})),t}return e}function g(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 b(){const e={},t=t=>e[t]=void 0;return{get:t=>e[t],set:(n,r)=>{e[n]=r,r.finally((()=>t(n)))}}}exports.createCache=m,exports.createShare=b,exports.createShortApi=h,exports.createShortMethods=l,exports.createSilentRefresh=function(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=[]})))}},exports.createSoon=function(e,t){const n=m(),r=b(),o=(o,s)=>new Promise(((a,c)=>{const f=new AbortController,u=e(o,s),[l,h]=p(u);console.log("[init]",l,h,u),h.signal=i([f.signal,h.signal]);const d={url:l,options:h},m=t({parsed:d}),b=g({url:d.url,...d.options,headers:new Headers(d.options?.headers)});if(d.options?.share){const e=r.get(b);if(e)return a(e)}if(d.options?.staleTime){const e=n.get(b);if(void 0!==e)return a(e)}console.log("abort",b,d.options),y(f,d.options?.aborts);const A=m(o,s);d.options?.share&&r.set(b,A),A.then((e=>{a(e),d.options?.staleTime&&n.set(b,e,(new Date).getTime()+d.options.staleTime)})).catch((e=>c(e)))})),s=h(((e,t,n,r,s,a,i)=>o(e,{...i,...a,method:t,params:n,query:r,body:s}))),a=l([...u,"head","options"],(e=>(t,n)=>o(t,{...n,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=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=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;
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ type SoonOptions = Omit<RequestInit, "body"> & {
3
3
  query?: Record<string, string | number | boolean | null | undefined | (string | number | boolean | null | undefined)[]> | URLSearchParams;
4
4
  params?: Record<string, string | number>;
5
5
  timeout?: number;
6
- aborts?: AbortController[];
6
+ aborts?: AbortController[] | never[];
7
7
  share?: boolean;
8
8
  staleTime?: number;
9
9
  };
@@ -24,6 +24,20 @@ declare function isBodyJson(body: any): boolean;
24
24
  declare function mergeOptions<Options extends SoonOptions>(...optionsList: (Options | undefined)[]): Options & {
25
25
  headers: Headers;
26
26
  };
27
+ declare function parseWithBase<Options extends SoonOptions>(urlOptions: {
28
+ url: string;
29
+ options?: Options;
30
+ baseURL?: string;
31
+ baseOptions?: Options;
32
+ }): {
33
+ url: string;
34
+ options: Options & {
35
+ headers: Headers;
36
+ body?: RequestInit["body"];
37
+ };
38
+ is_body_json: boolean;
39
+ abortController: AbortController;
40
+ };
27
41
  declare function parseUrlOptions<Options extends SoonOptions>(urlOptions: {
28
42
  url: string;
29
43
  options?: Options;
@@ -58,11 +72,13 @@ declare function raceAbort(abortController: AbortController, controllers?: Abort
58
72
  declare function deepSort(obj: unknown): unknown;
59
73
  declare function genRequestKey(req: {
60
74
  url: string;
61
- method?: string;
62
- headers?: Headers;
63
- body?: RequestInit["body"] | object;
64
- query?: Record<string, string | number | boolean | null | undefined | (string | number | boolean | null | undefined)[]> | URLSearchParams;
65
- params?: Record<string, string | number>;
75
+ options?: {
76
+ method?: string;
77
+ headers?: RequestInit["headers"];
78
+ body?: RequestInit["body"] | object;
79
+ query?: Record<string, string | number | boolean | null | undefined | (string | number | boolean | null | undefined)[]> | URLSearchParams;
80
+ params?: Record<string, string | number>;
81
+ };
66
82
  }): string;
67
83
  declare function createCache(): {
68
84
  get: (key: string) => unknown;
@@ -77,15 +93,18 @@ declare function createSilentRefresh(refresh_token_fn: () => Promise<void>): (su
77
93
  declare function createSoon<Options extends SoonOptions>(getConfig: (url: string, options?: Options) => {
78
94
  url: string;
79
95
  options?: Options;
80
- baseURL: string;
96
+ baseURL?: string;
81
97
  baseOptions?: Options;
82
98
  }, wrapper: (instance: {
83
99
  parsed: {
84
100
  url: string;
85
101
  options: Options & {
86
102
  headers: Headers;
87
- body?: BodyInit | null;
103
+ body?: RequestInit["body"];
88
104
  };
105
+ is_body_json: boolean;
106
+ abortController: AbortController;
107
+ requestKey: string;
89
108
  };
90
109
  }) => <T>(url: string, options?: Options) => Promise<T>): {
91
110
  options: <T>(url: string, options?: Options) => Promise<T>;
@@ -152,4 +171,4 @@ declare function createSoon<Options extends SoonOptions>(getConfig: (url: string
152
171
  request: <T>(url: string, options?: Options) => Promise<T>;
153
172
  };
154
173
 
155
- export { type SoonOptions, createCache, createShare, createShortApi, createShortMethods, createSilentRefresh, createSoon, deepSort, genRequestKey, isBodyJson, mergeHeaders, mergeOptions, mergeSignals, mergeUrl, parseUrlOptions, raceAbort };
174
+ export { type SoonOptions, createCache, createShare, createShortApi, createShortMethods, createSilentRefresh, createSoon, deepSort, genRequestKey, isBodyJson, mergeHeaders, mergeOptions, mergeSignals, mergeUrl, parseUrlOptions, parseWithBase, 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},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);return a.body=p?JSON.stringify(l):l,p&&a.headers.append("Content-Type","application/json"),a.signal=i([a.signal]),[u,a]}const l=["get","post","put","delete","patch"];function p(t,e){const n={};return t.forEach((t=>{n[t]=e(t)})),n}function h(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 l.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 y(t,e){e&&(e.pop()?.abort("soon-fetch race abort"),e.push(t))}function d(t){if(Array.isArray(t))return t.map(d);if("object"==typeof t&&null!==t){const e={};return Object.keys(t).sort().forEach((n=>{e[n]=d(t[n])})),e}return t}function g(t){const{url:e,headers:n,method:r,body:o,query:s,params:a}=t,i=d(Object.fromEntries(n?.entries()??[]));return(r??"get").toLowerCase()+e+JSON.stringify(d(s)??"")+JSON.stringify(d(a)??"")+JSON.stringify(i)+("object"==typeof o&&null!=o?JSON.stringify(d(o)):o)}function m(){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 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 A(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 S(t,e){const n=m(),r=b(),o=(o,s)=>new Promise(((a,c)=>{const f=new AbortController,l=t(o,s),[p,h]=u(l);console.log("[init]",p,h,l),h.signal=i([f.signal,h.signal]);const d={url:p,options:h},m=e({parsed:d}),b=g({url:d.url,...d.options,headers:new Headers(d.options?.headers)});if(d.options?.share){const t=r.get(b);if(t)return a(t)}if(d.options?.staleTime){const t=n.get(b);if(void 0!==t)return a(t)}console.log("abort",b,d.options),y(f,d.options?.aborts);const A=m(o,s);d.options?.share&&r.set(b,A),A.then((t=>{a(t),d.options?.staleTime&&n.set(b,t,(new Date).getTime()+d.options.staleTime)})).catch((t=>c(t)))})),s=h(((t,e,n,r,s,a,i)=>o(t,{...i,...a,method:e,params:n,query:r,body:s}))),a=p([...l,"head","options"],(t=>(e,n)=>o(e,{...n,method:t})));return{request:o,...s,...a}}export{m as createCache,b as createShare,h as createShortApi,p as createShortMethods,A as createSilentRefresh,S as createSoon,d as deepSort,g as genRequestKey,c as isBodyJson,a as mergeHeaders,f as mergeOptions,i as mergeSignals,s as mergeUrl,u as parseUrlOptions,y 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("?"),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};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "soon-fetch",
3
- "version": "3.0.0-beta.3",
4
- "description": "a 5Kb request lib alternative to axios",
3
+ "version": "3.0.0",
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",
7
7
  "module": "/dist/index.js",