fch 3.0.1 → 3.0.4
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/fetch.js +7 -13
- package/fetch.min.js +1 -0
- package/package.json +9 -6
- package/readme.md +59 -66
package/fetch.js
CHANGED
|
@@ -118,7 +118,7 @@ const create = (defaults = {}) => {
|
|
|
118
118
|
// Merge it, first the global and then the local
|
|
119
119
|
query = { ...fch.query, ...query };
|
|
120
120
|
// Absolute URL if possible; Default method; merge the default headers
|
|
121
|
-
request.url = createUrl(request.url
|
|
121
|
+
request.url = createUrl(request.url ?? fch.url, query, baseUrl);
|
|
122
122
|
request.method = (request.method ?? fch.method).toLowerCase();
|
|
123
123
|
request.headers = createHeaders(request.headers, fch.headers);
|
|
124
124
|
|
|
@@ -161,6 +161,7 @@ const create = (defaults = {}) => {
|
|
|
161
161
|
};
|
|
162
162
|
|
|
163
163
|
// Default values
|
|
164
|
+
fch.url = defaults.url ?? "/";
|
|
164
165
|
fch.method = defaults.method ?? "get";
|
|
165
166
|
fch.query = defaults.query ?? {};
|
|
166
167
|
fch.headers = defaults.headers ?? {};
|
|
@@ -189,18 +190,11 @@ const create = (defaults = {}) => {
|
|
|
189
190
|
fch.put = put;
|
|
190
191
|
fch.del = del;
|
|
191
192
|
|
|
193
|
+
fch.create = create;
|
|
194
|
+
|
|
192
195
|
return fch;
|
|
193
196
|
};
|
|
194
197
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// since the modules are deferred by default. Basically this is a big mess and
|
|
199
|
-
// I wish I could just do if `(typeof window !== 'undefined') window.fch = fch`,
|
|
200
|
-
// but unfortunately that's not possible now and I need this as a traditionally
|
|
201
|
-
// global definition, and then another file to import it as ESM. UGHHH
|
|
202
|
-
let glob = {};
|
|
203
|
-
if (typeof global !== "undefined") glob = global;
|
|
204
|
-
if (typeof window !== "undefined") glob = window;
|
|
205
|
-
glob.fch = create();
|
|
206
|
-
glob.fch.create = create;
|
|
198
|
+
const fch = create();
|
|
199
|
+
|
|
200
|
+
export default fch;
|
package/fetch.min.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const hasPlainBody=e=>{if(!e.headers["content-type"]&&["object","array"].includes(typeof e.body)&&!(e.body instanceof FormData))return!0},createUrl=(e,t,r)=>{let[o,a={}]=e.split("?");const s=new URLSearchParams({...Object.fromEntries(new URLSearchParams(t)),...Object.fromEntries(new URLSearchParams(a))}).toString();if(s&&(o=o+"?"+s),!r)return o;return new URL(o,r).href},createHeaders=(e,t)=>{const r={...t,...e};for(let e in r){const t=r[e];delete r[e],r[e.toLowerCase()]=t}return r},createDedupe=(e,t)=>({save:r=>(e.set(t,r),r),get:()=>e.get(t),clear:()=>e.delete(t)}),createFetch=(e,{after:t,dedupe:r,error:o,output:a})=>fetch(e.url,e).then((async e=>{r&&r.clear();let s={status:e.status,statusText:e.statusText,headers:{}};for(let t of e.headers.keys())s.headers[t.toLowerCase()]=e.headers.get(t);if(!e.ok){const t=new Error(e.statusText);return t.response=s,o(t)}const d=e.headers.get("content-type").includes("application/json");return s.body=await(d?e.json():e.text()),t&&(s=t(s)),"body"===a?s.body:s})),create=(e={})=>{const t=new Map,r=async(e,o={})=>{"object"!=typeof o&&(o={}),o="string"==typeof e?{url:e,...o}:e||{};let{dedupe:a=r.dedupe,output:s=r.output,baseURL:d=r.baseURL,baseUrl:n=d||r.baseUrl,query:u={},before:c=r.before,after:h=r.after,error:p=r.error,...l}=o;if(u={...r.query,...u},l.url=createUrl(l.url??r.url,u,n),l.method=(l.method??r.method).toLowerCase(),l.headers=createHeaders(l.headers,r.headers),"get"!==l.method&&(a=!1),a&&(a=createDedupe(t,l.url)),!["body","response"].includes(s)){throw new Error(`options.output needs to be either "body" (default) or "response", not "${s}"`)}return hasPlainBody(l)&&(l.body=JSON.stringify(l.body),l.headers["content-type"]="application/json; charset=utf-8"),c&&(l=c(l)),a&&!l.signal?a.get()?a.get():a.save(createFetch(l,{dedupe:a,output:s,error:p,after:h})):createFetch(l,{output:s,error:p,after:h})};r.url=e.url??"/",r.method=e.method??"get",r.query=e.query??{},r.headers=e.headers??{},r.dedupe=e.dedupe??!0,r.output=e.output??"body",r.credentials=e.credentials??"include",r.before=e.before??(e=>e),r.after=e.after??(e=>e),r.error=e.error??(e=>Promise.reject(e));return r.get=(e,t={})=>r(e,{...t}),r.head=(e,t={})=>r(e,{...t,method:"head"}),r.post=(e,t={})=>r(e,{...t,method:"post"}),r.patch=(e,t={})=>r(e,{...t,method:"patch"}),r.put=(e,t={})=>r(e,{...t,method:"put"}),r.del=(e,t={})=>r(e,{...t,method:"delete"}),r.create=create,r},fch=create();window.fch=fch;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fch",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
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",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"author": "Francisco Presencia <public@francisco.io> (https://francisco.io/)",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"scripts": {
|
|
12
|
+
"build": "terser --compress --mangle -- ./fetch.js | sed 's/export default fch;/window.fch=fch;/g' > ./fetch.min.js",
|
|
12
13
|
"start": "npm run watch # Start ~= Start dev",
|
|
13
14
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage --detectOpenHandles",
|
|
14
15
|
"watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch --coverage --detectOpenHandles"
|
|
@@ -21,19 +22,21 @@
|
|
|
21
22
|
"async",
|
|
22
23
|
"ajax"
|
|
23
24
|
],
|
|
24
|
-
"main": "./
|
|
25
|
-
"
|
|
25
|
+
"main": "./fetch.js",
|
|
26
|
+
"module": "./fetch.js",
|
|
27
|
+
"browser": "./fetch.min.js",
|
|
26
28
|
"files": [
|
|
27
|
-
"fetch.js"
|
|
29
|
+
"fetch.js",
|
|
30
|
+
"fetch.min.js"
|
|
28
31
|
],
|
|
29
32
|
"type": "module",
|
|
30
33
|
"engines": {
|
|
31
34
|
"node": ">=18.0.0"
|
|
32
35
|
},
|
|
33
|
-
"dependencies": {},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"jest": "^28.0.1",
|
|
36
|
-
"jest-fetch-mock": "^3.0.3"
|
|
38
|
+
"jest-fetch-mock": "^3.0.3",
|
|
39
|
+
"terser": "^5.13.1"
|
|
37
40
|
},
|
|
38
41
|
"jest": {
|
|
39
42
|
"testEnvironment": "jest-environment-node",
|
package/readme.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
# Fch [](https://www.npmjs.com/package/fch) [](https://github.com/franciscop/fetch/blob/master/fetch.js)
|
|
1
|
+
# Fch [](https://www.npmjs.com/package/fch) [](https://github.com/franciscop/fetch/blob/master/fetch.min.js)
|
|
2
2
|
|
|
3
3
|
A tiny library to make API calls easier. Similar to Axios, but tiny size and simpler API:
|
|
4
4
|
|
|
5
5
|
```js
|
|
6
6
|
import api from "fch";
|
|
7
|
-
|
|
7
|
+
api.baseUrl = "https://pokeapi.co/";
|
|
8
|
+
const mew = await api.get("/pokemon/150");
|
|
8
9
|
console.log(mew);
|
|
9
10
|
```
|
|
10
11
|
|
|
11
|
-
-
|
|
12
|
-
- Automatically
|
|
13
|
-
-
|
|
14
|
-
- Await/Async Promises
|
|
15
|
-
-
|
|
16
|
-
- Easily define shared options straight on the root `fch.baseUrl = "https://...";`.
|
|
17
|
-
- Interceptors: `before` (the request), `after` (the response) and `error` (it fails).
|
|
12
|
+
- Create instances with shared options across requests.
|
|
13
|
+
- Automatically encode object and array bodies as JSON.
|
|
14
|
+
- Automatically decode JSON responses based on the headers.
|
|
15
|
+
- Await/Async Promises; `>= 400 and <= 100` will _reject_ with an error.
|
|
16
|
+
- Interceptors: `before` the request, `after` the response and catch with `error`.
|
|
18
17
|
- Deduplicates parallel GET requests.
|
|
19
|
-
-
|
|
18
|
+
- Works the same way in Node.js and the browser.
|
|
19
|
+
- No dependencies; include it with a simple `<script>` on the browser.
|
|
20
20
|
|
|
21
21
|
```js
|
|
22
22
|
import api from 'fch';
|
|
@@ -31,18 +31,19 @@ api.del(url, { body, headers, ...options })
|
|
|
31
31
|
api.create({ url, body, headers, ...options})
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
| Options
|
|
35
|
-
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
39
|
-
| `
|
|
40
|
-
| `
|
|
41
|
-
| `
|
|
42
|
-
| `output`
|
|
43
|
-
| `
|
|
44
|
-
| `
|
|
45
|
-
| `
|
|
34
|
+
| Options | Default | Description |
|
|
35
|
+
| ----------------------| ----------| -----------------------------------|
|
|
36
|
+
| [`method`](#method) | `"get"` | Default method to use for the call |
|
|
37
|
+
| [`url`](#url) | `null` | The path or url for the request |
|
|
38
|
+
| [`baseUrl`](#url) | `null` | The shared base of the API |
|
|
39
|
+
| [`body`](#body) | `null` | The body to send with the request |
|
|
40
|
+
| [`query`](#query) | `{}` | Add query parameters to the URL |
|
|
41
|
+
| [`headers`](#headers) | `{}` | Shared headers across all requests |
|
|
42
|
+
| [`output`](#output) | `"body"` | The return value of the API call |
|
|
43
|
+
| [`dedupe`](#dedupe) | `true` | Reuse concurrently GET requests |
|
|
44
|
+
| [`before`](#interceptors) |`req => req` |Process the request before sending it |
|
|
45
|
+
| [`after`](#interceptors) |`res => res` |Process the response before returning it |
|
|
46
|
+
| [`error`](#interceptors) |`err => throw err` |Process errors before returning them |
|
|
46
47
|
|
|
47
48
|
## Getting Started
|
|
48
49
|
|
|
@@ -71,34 +72,30 @@ On the browser you can add it with a script and it will be available as `fch`:
|
|
|
71
72
|
|
|
72
73
|
## Options
|
|
73
74
|
|
|
75
|
+
These are all available options and their defaults:
|
|
76
|
+
|
|
74
77
|
```js
|
|
75
78
|
import api from 'fch';
|
|
76
79
|
|
|
77
80
|
// General options with their defaults; all of these are also parameters:
|
|
78
|
-
api.baseUrl = null; // Set an API base URL reused all across requests
|
|
79
81
|
api.method = 'get'; // Default method to use for api()
|
|
80
|
-
api.
|
|
81
|
-
api.
|
|
82
|
+
api.url = '/'; // Relative or absolute url where the request is sent
|
|
83
|
+
api.baseUrl = null; // Set an API base URL reused all across requests
|
|
84
|
+
api.query = {}; // Merged with the query parameters passed manually
|
|
85
|
+
api.headers = {}; // Merged with the headers on a per-request basis
|
|
82
86
|
|
|
83
87
|
// Control simple variables
|
|
84
|
-
api.dedupe = true; // Avoid parallel GET requests to the same path
|
|
85
88
|
api.output = 'body'; // Return the body; use 'response' for the full response
|
|
89
|
+
api.dedupe = true; // Avoid sending concurrent GET requests to the same path
|
|
86
90
|
|
|
87
91
|
// Interceptors
|
|
88
92
|
api.before = req => req;
|
|
89
93
|
api.after = res => res;
|
|
90
94
|
api.error = err => Promise.reject(err);
|
|
91
|
-
|
|
92
|
-
// Similar API to fetch()
|
|
93
|
-
api(url, { method, body, headers, ... });
|
|
94
|
-
|
|
95
|
-
// Our highly recommended style:
|
|
96
|
-
api.get(url, { headers, ... });
|
|
97
|
-
api.post(url, { body, headers, ... });
|
|
98
|
-
api.put(url, { body, headers, ... });
|
|
99
|
-
// ...
|
|
100
95
|
```
|
|
101
96
|
|
|
97
|
+
They can all be defined globally as shown above, passed manually as the options argument, or be used when [creating a new instance](#create-an-instance).
|
|
98
|
+
|
|
102
99
|
### Method
|
|
103
100
|
|
|
104
101
|
The HTTP method to make the request. When using the shorthand, it defaults to `GET`. We recommend using the method syntax:
|
|
@@ -350,32 +347,6 @@ fch.error = async err => {
|
|
|
350
347
|
```
|
|
351
348
|
|
|
352
349
|
|
|
353
|
-
### Define shared options
|
|
354
|
-
|
|
355
|
-
You can also define values straight away:
|
|
356
|
-
|
|
357
|
-
```js
|
|
358
|
-
import api from "fch";
|
|
359
|
-
|
|
360
|
-
api.baseUrl = "https://pokeapi.co/";
|
|
361
|
-
|
|
362
|
-
const mew = await api.get("/pokemon/150");
|
|
363
|
-
console.log(mew);
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
If you prefer Axios' style of outputting the whole response, you can do:
|
|
367
|
-
|
|
368
|
-
```js
|
|
369
|
-
// Default, already only returns the data on a successful call
|
|
370
|
-
api.output = "data";
|
|
371
|
-
const name = await api.get("/users/1").name;
|
|
372
|
-
|
|
373
|
-
// Axios-like
|
|
374
|
-
api.output = "response";
|
|
375
|
-
const name = await api.get("/users/1").data.name;
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
|
|
379
350
|
## How to
|
|
380
351
|
|
|
381
352
|
### Stop errors from throwing
|
|
@@ -427,7 +398,7 @@ const body = await fch.get('/blabla');
|
|
|
427
398
|
```
|
|
428
399
|
|
|
429
400
|
|
|
430
|
-
### Set
|
|
401
|
+
### Set authorization headers
|
|
431
402
|
|
|
432
403
|
You can set that globally as a header:
|
|
433
404
|
|
|
@@ -482,7 +453,9 @@ fch.get('/hello'); // Gets http://localhost:3000/hello (or wherever you are)
|
|
|
482
453
|
|
|
483
454
|
Note: for server-side (Node.js) usage, you always want to set `baseUrl`.
|
|
484
455
|
|
|
485
|
-
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
### Cancel ongoing requests
|
|
486
459
|
|
|
487
460
|
You can cancel ongoing requests [similarly to native fetch()](https://developer.mozilla.org/en-US/docs/Web/API/AbortController/abort#examples), by passing it a signal:
|
|
488
461
|
|
|
@@ -500,14 +473,34 @@ abortButton.addEventListener('click', () => {
|
|
|
500
473
|
api.get(url, { signal });
|
|
501
474
|
```
|
|
502
475
|
|
|
503
|
-
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
### Define shared options
|
|
479
|
+
|
|
480
|
+
You can also define values straight away:
|
|
481
|
+
|
|
482
|
+
```js
|
|
483
|
+
import api from "fch";
|
|
484
|
+
|
|
485
|
+
api.baseUrl = "https://pokeapi.co/";
|
|
486
|
+
|
|
487
|
+
const mew = await api.get("/pokemon/150");
|
|
488
|
+
console.log(mew);
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
You can also [create an instance](#create-an-instance) that will have the same options for all requests made with that instance.
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
### Node.js vs Browser
|
|
504
496
|
|
|
505
497
|
First, we use the native Node.js' fetch() and the browser's native fetch(), so any difference between those also applies to this library. For example, if you were to call `"/"` in the browser it'd refer to the current URL, while in Node.js it'd fail since you need to specify the full URL. Some other places where you might find differences: CORS, cache, etc.
|
|
506
498
|
|
|
507
499
|
In the library itself there's nothing different between the browser and Node.js, but it might be interesting to note that (if/when implemented) things like cache, etc. in Node.js are normally long-lived and shared, while in a browser request it'd bound to the request itself.
|
|
508
500
|
|
|
509
501
|
|
|
510
|
-
|
|
502
|
+
|
|
503
|
+
### Differences with Axios
|
|
511
504
|
|
|
512
505
|
The main difference is that things are simplified with fch:
|
|
513
506
|
|
|
@@ -525,7 +518,7 @@ axios.interceptors.request.use(fn);
|
|
|
525
518
|
fch.before = fn;
|
|
526
519
|
```
|
|
527
520
|
|
|
528
|
-
API size is also strikingly different, with **7.8kb** for Axios and **1.
|
|
521
|
+
API size is also strikingly different, with **7.8kb** for Axios and **1.1kb** for fch.
|
|
529
522
|
|
|
530
523
|
As disadvantages, I can think of two major ones for `fch`:
|
|
531
524
|
|