@@ -6,7 +6,7 @@ This package:
66
77+ Uses the modern, standardized ` fetch ` function.
88+ Does ** not** throw on non-OK HTTP responses.
9- + Allows to fully type all possible HTTP responses depending on the HTTP status code.
9+ + ** Allows to fully type all possible HTTP responses depending on the HTTP status code.**
1010
1111## Does a Non-OK Status Code Warrant an Error?
1212
@@ -35,16 +35,20 @@ npm i dr-fetch
3535### Create Custom Fetch Function
3636
3737This is optional and only needed if you need to do something before or after fetching. By far the most common task to
38- do is to add an authorization header to every call.
38+ do is to add the ` authorization ` header and the ` accept ` header to every call.
3939
4040``` typescript
4141// myFetch.ts
4242import { obtainToken } from ' ./magical-auth-stuff.js' ;
43+ import { setHeaders } from ' dr-fetch' ;
4344
4445export function myFetch(url : Parameters <typeof fetch >[0 ], init ? : Parameters <typeof fetch >[1 ]) {
4546 const token = obtainToken ();
46- // Add token to request headers. Not shown because it depends on whether init was given, whether init.headers is
47- // a POJO or not, etc. TypeScript will guide you through the possibilities.
47+ // Make sure there's an object where headers can be added:
48+ init ?? = {};
49+ // With setHeaders(), you can add headers to 'init' with a map, an array of tuples, a Headers
50+ // object or a POJO object.
51+ setHeaders (init , { ' Accept' : ' application/json' , ' Authorization' : ` Bearer ${token } ` });
4852 // Finally, do fetch.
4953 return fetch (url , init );
5054}
@@ -55,28 +59,34 @@ Think of this custom function as the place where you do interceptions (if you ar
5559### Create Fetcher Object
5660
5761``` typescript
62+ // fetcher.ts
5863import { DrFetch } from " dr-fetch" ;
5964import { myFetch } from " ./myFetch.js" ;
6065
61- const fetcher = new DrFetch (myFetch );
66+ export default new DrFetch (myFetch );
6267// If you don't need a custom fetch function, just do:
63- const fetcher = new DrFetch ();
68+ export default new DrFetch ();
6469```
6570
6671### Adding a Custom Body Parser
6772
73+ This step is also optional.
74+
6875One can say that the ` DrFetch ` class comes with 2 basic body parsers:
6976
70- 1 . JSON parser when the the value of the ` coontent-type ` response header is ` application/json ` or similar
77+ 1 . JSON parser when the value of the ` coontent-type ` response header is ` application/json ` or similar
7178(` application/problem+json ` , for instance).
72792 . Text parser when the value of the ` content-type ` response header is ` text/<something> ` , such as ` text/plain ` or
7380` text/csv ` .
7481
75- If your API sends a content type not included in any of the above two cases, use ` DrFetch.withParser() ` to add a custom
82+ If your API sends a content type not covered by any of the above two cases, use ` DrFetch.withParser() ` to add a custom
7683parser for the content type you are expecting. The class allows for fluent syntax, so you can chain calls:
7784
7885``` typescript
79- const fetcher = new DrFetch (myFetch )
86+ // fetcher.ts
87+ ...
88+
89+ export default new DrFetch (myFetch )
8090 .withParser (' custom/contentType' , async (response ) => {
8191 // Do what you must with the provided response object. In the end, you must return the parsed body.
8292 return finalBody ;
@@ -94,6 +104,7 @@ This is the fun part where we can enumerate the various shapes of the body depen
94104
95105``` typescript
96106import type { MyData } from " ./my-datatypes.js" ;
107+ import fetcher from ' ./fetcher.js' ;
97108
98109const response = await fetcher
99110 .for < 200 , MyData []> ()
@@ -180,22 +191,125 @@ accepts, such as `FormData`), no headers are explicitly specified and therefore
180191custom data-fetching function you provide) does in these cases.
181192
182193``` typescript
194+ import type { Todo } from ' ./myTypes.js' ;
195+
183196const newTodo = { text: ' I am new. Insert me!' };
184197const response = await fetcher
185- .for < 200 , { success: boolean ; }> ()
198+ .for < 200 , { success: true ; entity : Todo ; }> ()
186199 .for < 400 , { errors: string []; }> ()
187200 .post (' /api/todos' , newTodo );
188201
189202const newTodos = [{ text: ' I am new. Insert me!' }, { text: ' Me too!' }];
190203const response = await fetcher
191- .for < 200 , { success: boolean ; }> ()
204+ .for < 200 , { success: true ; entities : Todo [] ; }> ()
192205 .for < 400 , { errors: string []; }> ()
193206 .post (' /api/todos' , newTodos );
194207```
195208
196209As stated, your custom fetch can be used to further customize the request because these shortcut functions will, in the
197210end, call it.
198211
212+ ## setHeader and makeIterableHeaders
213+
214+ > Since ** v0.4.0**
215+
216+ These are two helper functions that assist you in writing custom data-fetching functions.
217+
218+ If you haven't realized, the ` init ` paramter in ` fetch() ` can have the headers specified in 3 different formats:
219+
220+ + As a ` Headers ` object (an instance of the ` Headers ` class)
221+ + As a POJO object, where the property key is the header name, and the property value is the header value
222+ + As an array of tuples of type ` [string, string] ` , where the first element is the header name, and the second one is
223+ its value
224+
225+ To further complicate this, the POJO object also accepts an array of strings as property values for headers that accept
226+ multiple values.
227+
228+ So writing a formal custom fetch ** without** ` setHeaders() ` looks like this:
229+
230+ ``` typescript
231+ export function myFetch(URL : Parameters <typeof fetch >[0 ], init ? : Parameters <typeof fetch >[1 ]) {
232+ const acceptHdrKey = ' Accept' ;
233+ const acceptHdrValue = ' application/json' ;
234+ init ?? = {};
235+ init .headers ?? = new Headers ();
236+ if (Array .isArray (init .headers )) {
237+ // Tuples, so push a tuple per desired header:
238+ init .headers .push ([acceptHdrKey , acceptHdrValue ]);
239+ }
240+ else if (init .headers instanceof Headers ) {
241+ init .headers .set (acceptHdrKey , acceptHdrValue );
242+ }
243+ else {
244+ // POJO object, so add headers as properties of an object:
245+ init .headers [acceptHdrKey ] = acceptHdrValue ;
246+ }
247+ return fetch (url , init );
248+ }
249+ ```
250+
251+ This would also get more complex if you account for multi-value headers. Now the same thing, using ` setHeaders() ` :
252+
253+ ``` typescript
254+ export function myFetch(URL : Parameters <typeof fetch >[0 ], init ? : Parameters <typeof fetch >[1 ]) {
255+ init ?? = {};
256+ setHeaders (init , [[' Accept' , ' application/json' ]]);
257+ // OR:
258+ setHeaders (init , new Map ([[' Accept' , [' application/json' , ' application/xml' ]]]));
259+ // OR:
260+ setHeaders (init , { ' Accept' : [' application/json' , ' application/xml' ] });
261+ // OR:
262+ setHeaders (init , new Headers ([[' Accept' , ' application/json' ]]));
263+ return fetch (url , init );
264+ }
265+ ```
266+
267+ The difference is indeed pretty shocking. Also note that adding arrays of values doesn't increase the complexity of
268+ the code.
269+
270+ ### makeIterableHeaders
271+
272+ This function is the magic trick that powers the ` setHeaders ` function, and is very handy for troubleshooting or unit
273+ testing because it can take a collection of HTTP header specifications in the form of a map, a Headers object, a POJO
274+ object or an array of tuples and return an iterator object that iterates through the definitions in the same way: A
275+ list of tuples.
276+
277+ ``` typescript
278+ const myHeaders1 = new Headers ();
279+ myHeaders1 .set (' Accept' , ' application/json' );
280+ myHeaders1 .set (' Authorization' , ' Bearer x' );
281+
282+ const myHeaders2 = new Map ();
283+ myHeaders2 .set (' Accept' , ' application/json' );
284+ myHeaders2 .set (' Authorization' , ' Bearer x' );
285+
286+ const myHeaders3 = {
287+ ' Accept' : ' application/json' ,
288+ ' Authorization' : ' Bearer x'
289+ };
290+
291+ const myHeaders4 = [
292+ [' Accept' , ' application/json' ],
293+ [' Authorization' , ' Bearer x' ],
294+ ];
295+
296+ // The output of these is identical.
297+ console .log ([... makeIterableHeaders (myHeaders1 )]);
298+ console .log ([... makeIterableHeaders (myHeaders2 )]);
299+ console .log ([... makeIterableHeaders (myHeaders3 )]);
300+ console .log ([... makeIterableHeaders (myHeaders4 )]);
301+ ```
302+
303+ This function is a ** generator function** , so what returns is an iterator object. The two most helpful ways of using
304+ it are in ` for..of ` statements and spreading:
305+
306+ ``` typescript
307+ for (let [key, value] of makeIterableHeaders (myHeaders )) { ... }
308+
309+ // In unit-testing, perhaps:
310+ expect ([... makeIterableHeaders (myHeaders )].length ).to .equal (2 );
311+ ```
312+
199313## Usage Without TypeScript (JavaScript Projects)
200314
201315Why are you a weird fellow/gal? Anyway, prejudice aside, body typing will mean nothing to you, so forget about ` for() `
0 commit comments