fch 6.0.1 → 6.0.3
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/index.d.ts +11 -11
- package/index.min.js +1 -1
- package/package.json +1 -1
- package/readme.md +15 -38
package/index.d.ts
CHANGED
|
@@ -32,25 +32,25 @@ type Options = {
|
|
|
32
32
|
[key: string]: any;
|
|
33
33
|
};
|
|
34
34
|
interface FchInstance {
|
|
35
|
-
(url?: string, options?: Options): Promise<
|
|
35
|
+
<T = any>(url?: string, options?: Options): Promise<T>;
|
|
36
36
|
create: (options?: Options) => FchInstance;
|
|
37
|
-
get: (url: string, options?: Options) => Promise<
|
|
38
|
-
head: (url: string, options?: Options) => Promise<
|
|
39
|
-
post: (url: string, body?: Body, options?: Options) => Promise<
|
|
40
|
-
patch: (url: string, body?: Body, options?: Options) => Promise<
|
|
41
|
-
put: (url: string, body?: Body, options?: Options) => Promise<
|
|
42
|
-
delete: (url: string, options?: Options) => Promise<
|
|
43
|
-
del: (url: string, options?: Options) => Promise<
|
|
37
|
+
get: <T = any>(url: string, options?: Options) => Promise<T>;
|
|
38
|
+
head: <T = any>(url: string, options?: Options) => Promise<T>;
|
|
39
|
+
post: <T = any>(url: string, body?: Body, options?: Options) => Promise<T>;
|
|
40
|
+
patch: <T = any>(url: string, body?: Body, options?: Options) => Promise<T>;
|
|
41
|
+
put: <T = any>(url: string, body?: Body, options?: Options) => Promise<T>;
|
|
42
|
+
delete: <T = any>(url: string, options?: Options) => Promise<T>;
|
|
43
|
+
del: <T = any>(url: string, options?: Options) => Promise<T>;
|
|
44
44
|
text: () => Promise<string>;
|
|
45
|
-
json: () => Promise<
|
|
45
|
+
json: <T = any>() => Promise<T>;
|
|
46
46
|
blob: () => Promise<Blob>;
|
|
47
47
|
stream: () => ReadableStream | null;
|
|
48
48
|
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
49
49
|
formData: () => Promise<FormData>;
|
|
50
|
-
body: () => Promise<
|
|
50
|
+
body: <T = any>() => Promise<T>;
|
|
51
51
|
clone: () => Response;
|
|
52
52
|
raw: () => Response;
|
|
53
|
-
response: () => Promise<
|
|
53
|
+
response: <T = any>() => Promise<T>;
|
|
54
54
|
url: string;
|
|
55
55
|
method: Methods;
|
|
56
56
|
query: Query;
|
package/index.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var S=async(i)=>(i=await i,Array.isArray(i)?await Promise.all(i.map(S)):i),N=(i,w)=>(...x)=>((n)=>n instanceof RegExp?n.test.bind(n):n)(i).call(w,...x),O=(i,w)=>async(x,n,m)=>({value:x,extra:await N(i,w)(x,n,m)}),_=({extra:i})=>i,D=({value:i})=>i,Q={every:async(i,w,x)=>{for(let n=0;n<i.length;n++)if(!await N(w,x)(i[n],n,i))return!1;return!0},filter:async(i,w,x)=>(await S(i.map(O(w,x)))).filter(_).map(D),find:async(i,w,x)=>{for(let n=0;n<i.length;n++)if(await N(w,x)(i[n],n,i))return i[n]},findIndex:async(i,w,x)=>{for(let n=0;n<i.length;n++)if(await N(w,x)(i[n],n,i))return n;return-1},forEach:async(i,w,x)=>(await S(i.map(O(w,x))),i),reduce:async(i,w,x)=>{let n=x!==void 0;n||(x=i[0]);for(let m=n?0:1;m<i.length;m++)x=await N(w)(x,i[m],m,i);return x},reduceRight:async(i,w,x)=>{let n=x!==void 0;n||(x=i[i.length-1]);for(let m=i.length-(n?1:2);m>=0;m--)x=await N(w)(x,i[m],m,i);return x},some:async(i,w,x)=>{for(let n=0;n<i.length;n++)if(await N(w,x)(i[n],n,i))return!0;return!1}},T=(i,w)=>(x,n)=>n==="then"?(...m)=>S(i).then(...m):n==="catch"?(...m)=>Y(S(i).catch(...m)):v(S(i).then((m)=>typeof n=="symbol"?m[n]:(n in w)?v((...P)=>w[n](m,...P),w):typeof m=="number"&&(n in w.number)?v((...P)=>w.number[n](m,...P),w):typeof m=="string"&&(n in w.string)?v((...P)=>w.string[n](m,...P),w):Array.isArray(m)&&(n in w.array)?v((...P)=>w.array[n](m,...P),w):m[n]&&m[n].bind?v(m[n].bind(m),w):v(m[n],w)),w),F=(i,w)=>(x,n,m)=>v(S(i).then((P)=>{return typeof P!="function"?(g=`You tried to call "${JSON.stringify(P)}" (${typeof P}) as a function, but it is not.`,Promise.reject(Error(g))):P(...m);var g}),w),v=(i,w)=>new Proxy(()=>{},{get:T(i,w),apply:F(i,w)});function Y(i,{number:w,string:x,array:n,...m}={}){return typeof i=="function"?(...P)=>Y(Promise.all(P).then((g)=>i(...g)),{number:w,string:x,array:n,...m}):new Proxy({},{get:T(i,{number:{...w},string:{...x},array:{...Q,...n},...m})})}var H=(i)=>{if(!i)return!1;if(i instanceof FormData)return!1;if(typeof(i.pipe||i.pipeTo)==="function")return!1;return typeof i==="object"||Array.isArray(i)},G=(i)=>{if(typeof i!=="object")return i;for(let w in i)if(i[w]===void 0)delete i[w];return i};class W extends Error{response;constructor(i){let w="Error "+i.status;super(w);this.response=i,this.message=w}}var R=(i,w,x)=>{let[n,m=""]=i.split("?"),P=new URLSearchParams(Object.fromEntries([...new URLSearchParams(G(w)),...new URLSearchParams(G(m))])).toString();if(P)n=n+"?"+P;if(!x)return n;return new URL(n.replace(/^\//,""),x).href},B=(i)=>{let w={};for(let[x,n]of Object.entries(i))w[x.toLowerCase()]=n;return w},X=async(i)=>{let w=i.headers.get("content-type"),x=w&&w.includes("application/json"),n=await i.clone().text();return x?JSON.parse(n):n},Z=async(i)=>{let w={status:i.status,statusText:i.statusText,headers:{},body:void 0};if(i.headers.forEach((x,n)=>{w.headers[n.toLowerCase()]=x}),!i.ok)throw new W(i);return w.body=await X(i),w},V=(i,{ref:w,after:x,error:n,output:m})=>{return fetch(i.url,i).then(async(P)=>{if(w.res=P,P.ok&&m==="stream")return P.body;if(P.ok&&P[m]&&typeof P[m]==="function")return P[m]();let g=x(await Z(P));if(m==="body")return g.body;else if(m==="response")return g;else if(m==="raw")return P.clone();else throw Error(`Invalid option output="${m}"`)}).catch(n)};function I(i={}){let w={},x={},m=Y(async(P="/",g={})=>{let{output:$,before:J,after:K,error:L,cache:z,...A}={...m,...g};if(A.url=R(P,{...m.query,...g.query},A.baseUrl??A.baseURL),A.method=(A.method||"get").toLowerCase(),A.headers=B({...m.headers,...g.headers}),typeof SubmitEvent<"u"&&A.body instanceof SubmitEvent||typeof HTMLFormElement<"u"&&A.body instanceof HTMLFormElement)A.body=new FormData(A.body);if(H(A.body))A.body=JSON.stringify(G(A.body)),A.headers["content-type"]="application/json";if(A=J?J(A):A,!z||A.method!=="get")return V(A,{ref:x,output:$,error:L,after:K});let E=A.method+":"+A.url,M=await z.get(E);if(M)return M;if(w[E])return w[E];let C;try{w[E]=V(A,{ref:x,output:$,error:L,after:K}),C=await w[E]}finally{delete w[E]}return await z.set(E,C),C},{text:()=>x.res.clone().text(),json:()=>x.res.clone().json(),blob:()=>x.res.clone().blob(),stream:()=>x.res.clone().body,arrayBuffer:()=>x.res.clone().arrayBuffer(),formData:()=>x.res.clone().formData(),body:()=>X(x.res.clone()),clone:()=>x.res.clone(),raw:()=>x.res.clone(),response:()=>Z(x.res.clone())});return m.url=i.url??"/",m.method=i.method??"get",m.query=i.query??{},m.headers=i.headers??{},m.baseUrl=i.baseUrl??i.baseURL??null,m.baseURL=i.baseUrl??i.baseURL??null,m.cache=i.cache??null,m.output=i.output??"body",m.credentials=i.credentials??"include",m.before=i.before??((P)=>P),m.after=i.after??((P)=>P),m.error=i.error??((P)=>Promise.reject(P)),m.get=(P,g)=>m(P,{method:"get",...g}),m.head=(P,g)=>m(P,{method:"head",...g}),m.post=(P,g,$)=>m(P,{method:"post",body:g,...$}),m.patch=(P,g,$)=>m(P,{method:"patch",body:g,...$}),m.put=(P,g,$)=>m(P,{method:"put",body:g,...$}),m.delete=(P,g)=>m(P,{method:"delete",...g}),m.del=m.delete,m.create=I,m}if(typeof window<"u")window.fch=I();var j=I();export{j as default,I as create};
|
|
1
|
+
var S=async(i)=>(i=await i,Array.isArray(i)?await Promise.all(i.map(S)):i),N=(i,w)=>(...x)=>((n)=>n instanceof RegExp?n.test.bind(n):n)(i).call(w,...x),O=(i,w)=>async(x,n,m)=>({value:x,extra:await N(i,w)(x,n,m)}),_=({extra:i})=>i,D=({value:i})=>i,Q={every:async(i,w,x)=>{for(let n=0;n<i.length;n++)if(!await N(w,x)(i[n],n,i))return!1;return!0},filter:async(i,w,x)=>(await S(i.map(O(w,x)))).filter(_).map(D),find:async(i,w,x)=>{for(let n=0;n<i.length;n++)if(await N(w,x)(i[n],n,i))return i[n]},findIndex:async(i,w,x)=>{for(let n=0;n<i.length;n++)if(await N(w,x)(i[n],n,i))return n;return-1},forEach:async(i,w,x)=>(await S(i.map(O(w,x))),i),reduce:async(i,w,x)=>{let n=x!==void 0;n||(x=i[0]);for(let m=n?0:1;m<i.length;m++)x=await N(w)(x,i[m],m,i);return x},reduceRight:async(i,w,x)=>{let n=x!==void 0;n||(x=i[i.length-1]);for(let m=i.length-(n?1:2);m>=0;m--)x=await N(w)(x,i[m],m,i);return x},some:async(i,w,x)=>{for(let n=0;n<i.length;n++)if(await N(w,x)(i[n],n,i))return!0;return!1}},T=(i,w)=>(x,n)=>n==="then"?(...m)=>S(i).then(...m):n==="catch"?(...m)=>Y(S(i).catch(...m)):v(S(i).then((m)=>typeof n=="symbol"?m[n]:(n in w)?v((...P)=>w[n](m,...P),w):typeof m=="number"&&(n in w.number)?v((...P)=>w.number[n](m,...P),w):typeof m=="string"&&(n in w.string)?v((...P)=>w.string[n](m,...P),w):Array.isArray(m)&&(n in w.array)?v((...P)=>w.array[n](m,...P),w):m[n]&&m[n].bind?v(m[n].bind(m),w):v(m[n],w)),w),F=(i,w)=>(x,n,m)=>v(S(i).then((P)=>{return typeof P!="function"?(g=`You tried to call "${JSON.stringify(P)}" (${typeof P}) as a function, but it is not.`,Promise.reject(Error(g))):P(...m);var g}),w),v=(i,w)=>new Proxy(()=>{},{get:T(i,w),apply:F(i,w)});function Y(i,{number:w,string:x,array:n,...m}={}){return typeof i=="function"?(...P)=>Y(Promise.all(P).then((g)=>i(...g)),{number:w,string:x,array:n,...m}):new Proxy({},{get:T(i,{number:{...w},string:{...x},array:{...Q,...n},...m})})}var H=(i)=>{if(!i)return!1;if(i instanceof FormData)return!1;if(typeof(i.pipe||i.pipeTo)==="function")return!1;return typeof i==="object"||Array.isArray(i)},G=(i)=>{if(typeof i!=="object")return i;for(let w in i)if(i[w]===void 0)delete i[w];return i};class W extends Error{response;constructor(i){let w="Error "+i.status;super(w);this.response=i,this.message=w}}var R=(i,w,x)=>{let[n,m=""]=i.split("?"),P=new URLSearchParams(Object.fromEntries([...new URLSearchParams(G(w)),...new URLSearchParams(G(m))])).toString();if(P)n=n+"?"+P;if(!x)return n;return new URL(n.replace(/^\//,""),x.replace(/\/$/,"")+"/").href},B=(i)=>{let w={};for(let[x,n]of Object.entries(i))w[x.toLowerCase()]=n;return w},X=async(i)=>{let w=i.headers.get("content-type"),x=w&&w.includes("application/json"),n=await i.clone().text();return x?JSON.parse(n):n},Z=async(i)=>{let w={status:i.status,statusText:i.statusText,headers:{},body:void 0};if(i.headers.forEach((x,n)=>{w.headers[n.toLowerCase()]=x}),!i.ok)throw new W(i);return w.body=await X(i),w},V=(i,{ref:w,after:x,error:n,output:m})=>{return fetch(i.url,i).then(async(P)=>{if(w.res=P,P.ok&&m==="stream")return P.body;if(P.ok&&P[m]&&typeof P[m]==="function")return P[m]();let g=x(await Z(P));if(m==="body")return g.body;else if(m==="response")return g;else if(m==="raw")return P.clone();else throw Error(`Invalid option output="${m}"`)}).catch(n)};function I(i={}){let w={},x={},m=Y(async(P="/",g={})=>{let{output:$,before:J,after:K,error:L,cache:z,...A}={...m,...g};if(A.url=R(P,{...m.query,...g.query},A.baseUrl??A.baseURL),A.method=(A.method||"get").toLowerCase(),A.headers=B({...m.headers,...g.headers}),typeof SubmitEvent<"u"&&A.body instanceof SubmitEvent||typeof HTMLFormElement<"u"&&A.body instanceof HTMLFormElement)A.body=new FormData(A.body);if(H(A.body))A.body=JSON.stringify(G(A.body)),A.headers["content-type"]="application/json";if(A=J?J(A):A,!z||A.method!=="get")return V(A,{ref:x,output:$,error:L,after:K});let E=A.method+":"+A.url,M=await z.get(E);if(M)return M;if(w[E])return w[E];let C;try{w[E]=V(A,{ref:x,output:$,error:L,after:K}),C=await w[E]}finally{delete w[E]}return await z.set(E,C),C},{text:()=>x.res.clone().text(),json:()=>x.res.clone().json(),blob:()=>x.res.clone().blob(),stream:()=>x.res.clone().body,arrayBuffer:()=>x.res.clone().arrayBuffer(),formData:()=>x.res.clone().formData(),body:()=>X(x.res.clone()),clone:()=>x.res.clone(),raw:()=>x.res.clone(),response:()=>Z(x.res.clone())});return m.url=i.url??"/",m.method=i.method??"get",m.query=i.query??{},m.headers=i.headers??{},m.baseUrl=i.baseUrl??i.baseURL??null,m.baseURL=i.baseUrl??i.baseURL??null,m.cache=i.cache??null,m.output=i.output??"body",m.credentials=i.credentials??"include",m.before=i.before??((P)=>P),m.after=i.after??((P)=>P),m.error=i.error??((P)=>Promise.reject(P)),m.get=(P,g)=>m(P,{method:"get",...g}),m.head=(P,g)=>m(P,{method:"head",...g}),m.post=(P,g,$)=>m(P,{method:"post",body:g,...$}),m.patch=(P,g,$)=>m(P,{method:"patch",body:g,...$}),m.put=(P,g,$)=>m(P,{method:"put",body:g,...$}),m.delete=(P,g)=>m(P,{method:"delete",...g}),m.del=m.delete,m.create=I,m}if(typeof window<"u")window.fch=I();var j=I();export{j as default,I as create};
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -5,13 +5,13 @@ A tiny library to make API calls easier. Similar to Axios, but tiny size and sim
|
|
|
5
5
|
```js
|
|
6
6
|
// Plain usage
|
|
7
7
|
import fch from "fch";
|
|
8
|
-
const mew = await fch("https://pokeapi.co/pokemon/
|
|
8
|
+
const mew = await fch("https://pokeapi.co/pokemon/151");
|
|
9
9
|
console.log(mew);
|
|
10
10
|
|
|
11
11
|
// As an API abstraction
|
|
12
12
|
const api = fch.create({ baseUrl: "https://pokeapi.co/" });
|
|
13
|
-
const mew = await api.get("/pokemon/
|
|
14
|
-
await api.patch("/pokemon/
|
|
13
|
+
const mew = await api.get("/pokemon/151");
|
|
14
|
+
await api.patch("/pokemon/151", { type: "psychic" });
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
- Create instances with shared options across requests.
|
|
@@ -328,44 +328,24 @@ stream.pipeTo(...);
|
|
|
328
328
|
|
|
329
329
|
> We use **[polystore](https://polystore.dev/)** for Cache management.
|
|
330
330
|
|
|
331
|
-
The cache (disabled by default) is a great method to reduce the number of API requests we make. Provide a [polystore](https://polystore.dev/) Store instance directly to `fch.create()`
|
|
331
|
+
The cache (disabled by default) is a great method to reduce the number of API requests we make. Provide a [polystore](https://polystore.dev/) Store instance directly to `fch.create()` and fch will cache GET responses using whatever persistence that store offers. Set `cache` to `null` on a call to skip caching entirely.
|
|
332
332
|
|
|
333
|
+
While a GET request is in flight, fch keeps an internal map of ongoing requests to deduplicate parallel calls. As soon as the request resolves, the result is written to your store (if available) and the entry is removed from the in-flight map.
|
|
333
334
|
|
|
334
|
-
|
|
335
|
-
While a GET request is in flight, fch keeps an internal map of ongoing requests so additional calls reuse the same promise instead of triggering duplicate fetches. As soon as the request resolves, the result is written to your store (if available) and the entry is removed from the in-flight map.
|
|
336
|
-
|
|
337
|
-
To activate the cache, create a polystore store and pass it to fch:
|
|
335
|
+
To activate the cache, create a **polystore** store and pass it to fch:
|
|
338
336
|
|
|
339
337
|
```js
|
|
340
338
|
import kv from "polystore";
|
|
341
339
|
|
|
342
340
|
// This API reuses responses using an in-memory Map:
|
|
343
|
-
const cache = kv(new Map());
|
|
344
|
-
const api = fch.create({
|
|
345
|
-
cache,
|
|
346
|
-
baseUrl: "https://api.myweb.com/",
|
|
347
|
-
});
|
|
341
|
+
const cache = kv(new Map(), { expires: '1h' });
|
|
342
|
+
const api = fch.create({ baseUrl: "...", cache });
|
|
348
343
|
|
|
349
|
-
//
|
|
350
|
-
const shortCache =
|
|
344
|
+
// Call `.expires()` to return an instance with a different cache time
|
|
345
|
+
const shortCache = cache.expires('10min');
|
|
351
346
|
api.get("/somedata", { cache: shortCache });
|
|
352
347
|
```
|
|
353
348
|
|
|
354
|
-
For more control, you can use **polystore** to create a custom cache store with different backends (in-memory, Redis, localStorage, etc.):
|
|
355
|
-
|
|
356
|
-
```js
|
|
357
|
-
import fch from "fch";
|
|
358
|
-
import kv from "polystore";
|
|
359
|
-
|
|
360
|
-
// In-memory cache backed by a Map
|
|
361
|
-
const cache = kv(new Map());
|
|
362
|
-
|
|
363
|
-
const api = fch.create({
|
|
364
|
-
baseUrl: "https://api.myweb.com/",
|
|
365
|
-
cache,
|
|
366
|
-
});
|
|
367
|
-
```
|
|
368
|
-
|
|
369
349
|
Polystore supports multiple backends. For example, with Redis:
|
|
370
350
|
|
|
371
351
|
```js
|
|
@@ -374,15 +354,12 @@ import kv from "polystore";
|
|
|
374
354
|
import { createClient } from "redis";
|
|
375
355
|
|
|
376
356
|
// Redis-backed cache store
|
|
377
|
-
const
|
|
378
|
-
const cache = kv(redis);
|
|
357
|
+
const cache = kv(createClient().connect());
|
|
379
358
|
|
|
380
|
-
const api = fch.create({
|
|
381
|
-
cache,
|
|
382
|
-
});
|
|
359
|
+
const api = fch.create({ cache });
|
|
383
360
|
```
|
|
384
361
|
|
|
385
|
-
That's the basic usage, but "invalidating cache" is not one of the complex topics in CS for no reason.
|
|
362
|
+
That's the basic usage, but "invalidating cache" is not one of the complex topics in CS for no reason. To clear the cache, call `cache.clear()` at any time:
|
|
386
363
|
|
|
387
364
|
```js
|
|
388
365
|
import kv from "polystore";
|
|
@@ -412,7 +389,7 @@ await redis.flushDB();
|
|
|
412
389
|
|
|
413
390
|
#### Creating a custom store
|
|
414
391
|
|
|
415
|
-
You can create a custom store that works with fch by implementing the polystore-compatible interface. See the [polystore documentation](https://polystore.dev/) for details on creating custom stores and adapters.
|
|
392
|
+
You can create a custom store that works with fch by implementing the polystore-compatible interface. See the [polystore documentation](https://polystore.dev/documentation#custom-store) for details on creating custom stores and adapters.
|
|
416
393
|
|
|
417
394
|
### Interceptors
|
|
418
395
|
|
|
@@ -617,7 +594,7 @@ import api from "fch";
|
|
|
617
594
|
|
|
618
595
|
api.baseUrl = "https://pokeapi.co/";
|
|
619
596
|
|
|
620
|
-
const mew = await api.get("/pokemon/
|
|
597
|
+
const mew = await api.get("/pokemon/151");
|
|
621
598
|
console.log(mew);
|
|
622
599
|
```
|
|
623
600
|
|