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 CHANGED
@@ -32,25 +32,25 @@ type Options = {
32
32
  [key: string]: any;
33
33
  };
34
34
  interface FchInstance {
35
- (url?: string, options?: Options): Promise<any>;
35
+ <T = any>(url?: string, options?: Options): Promise<T>;
36
36
  create: (options?: Options) => FchInstance;
37
- get: (url: string, options?: Options) => Promise<any>;
38
- head: (url: string, options?: Options) => Promise<any>;
39
- post: (url: string, body?: Body, options?: Options) => Promise<any>;
40
- patch: (url: string, body?: Body, options?: Options) => Promise<any>;
41
- put: (url: string, body?: Body, options?: Options) => Promise<any>;
42
- delete: (url: string, options?: Options) => Promise<any>;
43
- del: (url: string, options?: Options) => Promise<any>;
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<any>;
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<any>;
50
+ body: <T = any>() => Promise<T>;
51
51
  clone: () => Response;
52
52
  raw: () => Response;
53
- response: () => Promise<any>;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fch",
3
- "version": "6.0.1",
3
+ "version": "6.0.3",
4
4
  "description": "Fetch interface with better promises, deduplication, defaults, etc.",
5
5
  "homepage": "https://github.com/franciscop/fetch",
6
6
  "repository": "https://github.com/franciscop/fetch.git",
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/150");
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/150");
14
- await api.patch("/pokemon/150", { type: "psychic" });
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()` (or per request) and fch will cache GET responses using whatever persistence that store offers. Set `cache` to `null` on a call to skip caching entirely. Legacy cache helpers such as `shouldCache`, `generateKey`, or expiry options have been removed; fch now relies purely on the semantics of the Polystore Store you pass in.
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
- // Provide a different store per request if needed
350
- const shortCache = kv(new Map());
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 redis = await createClient().connect();
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. Let's dig deeper. To clear the cache, you can call `cache.clear()` at any time:
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/150");
597
+ const mew = await api.get("/pokemon/151");
621
598
  console.log(mew);
622
599
  ```
623
600