Skip to content

Commit 95e7422

Browse files
authored
feat: Open the DrFetch class to any status code (#23)
1 parent 069fee9 commit 95e7422

File tree

3 files changed

+41
-25
lines changed

3 files changed

+41
-25
lines changed

README.md

Lines changed: 33 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# dr-fetch
22

3-
This is one more package for fetching. The difference with other packages is that this one does it right.
3+
This is not just one more wrapper for `fetch()`: This package promotes the idea of using customized data-fetching
4+
functions, which is the most maintainable option, and adds features no other wrapper provides to date.
45

56
This package:
67

78
+ Uses the modern, standardized `fetch` function.
89
+ Does **not** throw on non-OK HTTP responses.
9-
+ **Allows to fully type all possible HTTP responses depending on the HTTP status code.**
10+
+ **Can fully type all possible HTTP responses depending on the HTTP status code, even non-standard ones like 499.**
11+
+ Works in any runtime that implements `fetch()` (browsers, NodeJS, etc.).
1012
+ Probably the tiniest fetch wrapper you'll ever need.
1113

1214
## Does a Non-OK Status Code Warrant an Error?
@@ -33,7 +35,7 @@ smell: `try..catch` is being used as a branching mechanism.
3335
npm i dr-fetch
3436
```
3537

36-
### Create Custom Fetch Function
38+
### Create a Custom Fetch Function
3739

3840
This is optional and only needed if you need to do something before or after fetching. By far the most common task to
3941
do is to add the `authorization` header and the `accept` header to every call.
@@ -47,10 +49,7 @@ export function myFetch(url: FetchFnUrl, init?: FetchFnInit) {
4749
const token = obtainToken();
4850
// Make sure there's an object where headers can be added:
4951
init ??= {};
50-
// With setHeaders(), you can add headers to 'init' with a map, an array of tuples, a Headers
51-
// object or a POJO object.
5252
setHeaders(init, { Accept: 'application/json', Authorization: `Bearer ${token}`});
53-
// Finally, do fetch.
5453
return fetch(url, init);
5554
}
5655
```
@@ -137,11 +136,29 @@ else {
137136
}
138137
```
139138

139+
## Typing For Non-Standard Status Codes
140+
141+
> Since **v0.8.0**
142+
143+
This library currently supports, out of the box, the OK status codes, client error status codes and server error status
144+
codes that the MDN website list, and are therefore considered standardized.
145+
146+
If you need to type a response based on any other status code not currently supported, just do something like this:
147+
148+
```typescript
149+
import { DrFetch, type StatusCode } from "dr-fetch";
150+
151+
type MyStatusCode = StatusCode | 499;
152+
export default new DrFetch<MyStatusCode>();
153+
```
154+
155+
You will now be able to use non-standardized status code `499` to type the response body with `DrFetch.for<>()`.
156+
140157
## Smarter Uses
141158

142159
It is smart to create just one fetcher, configure it, then use it for every fetch call. Because generally speaking,
143-
different URL's will carry a different body type, the fetcher object should be kept free of `for<>()` calls. However,
144-
what if your API is standardized so all status `400` bodies look the same? Then configure that type:
160+
different URL's will carry a different body type, the fetcher object should be kept free of `for<>()` calls. But what
161+
if your API is standardized so all status `400` bodies look the same? Then configure that type:
145162

146163
```typescript
147164
// root-fetcher.ts
@@ -226,7 +243,7 @@ end, call it.
226243
227244
These are two helper functions that assist you in writing custom data-fetching functions.
228245

229-
If you haven't realized, the `init` paramter in `fetch()` can have the headers specified in 3 different formats:
246+
If you haven't realized, the `init` parameter in `fetch()` can have the headers specified in 3 different formats:
230247

231248
+ As a `Headers` object (an instance of the `Headers` class)
232249
+ As a POJO object, where the property key is the header name, and the property value is the header value
@@ -261,7 +278,7 @@ export function myFetch(URL: FetchFnUrl, init?: FetchFnInit) {
261278
}
262279
```
263280

264-
This would also get more complex if you account for multi-value headers.
281+
This would also get more complex if you account for multi-value headers. The bottom line is: This is complex.
265282

266283
Now the same thing, using `setHeaders()`:
267284

@@ -280,14 +297,17 @@ export function myFetch(URL: FetchFnUrl, init?: FetchFnInit) {
280297
return fetch(url, init);
281298
}
282299
```
300+
> [!NOTE]
301+
> With `setHeaders()`, you can add headers to 'init' with a map, an array of tuples, a `Headers` instance or a POJO
302+
> object.
283303
284304
The difference is indeed pretty shocking: One line of code and you are done. Also note that adding arrays of values
285305
doesn't increase the complexity of the code: It's still one line.
286306

287307
### makeIterableHeaders
288308

289309
This function is the magic trick that powers the `setHeaders` function, and is very handy for troubleshooting or unit
290-
testing because it can take a collection of HTTP header specifications in the form of a map, a Headers object, a POJO
310+
testing because it can take a collection of HTTP header specifications in the form of a map, a `Headers` object, a POJO
291311
object or an array of tuples and return an iterator object that iterates through the definitions in the same way: A
292312
list of tuples.
293313

@@ -402,16 +422,14 @@ carry the class instance in the `body` property.
402422
.for<200, DownloadProgress>()
403423
.get('https://example.com/my-video.mp4')
404424
)
405-
.body
406-
;
407-
}
425+
.body;
426+
}
408427
</script>
409428
410429
<button type="button" onclick={startDownload}>
411430
Start Download
412431
</button>
413432
<progress value={download?.progress ?? 0}></progress>
414-
415433
```
416434

417435
When the button is clicked, the download is started. The custom processor simply creates the new instance of the

src/DrFetch.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ function textParser(response: Response) {
104104
* existing one using the parent's `clone` function. When cloning, pass a new data-fetching function (if required) so
105105
* the clone uses this one instead of the one of the parent fetcher.
106106
*/
107-
export class DrFetch<T = unknown> {
107+
export class DrFetch<TStatusCode extends number = StatusCode, T = unknown> {
108108
#fetchFn: FetchFn;
109109
#customProcessors: [string | RegExp, (response: Response, stockParsers: { json: BodyParserFn<any>; text: BodyParserFn<string>; }) => Promise<any>][] = [];
110110

@@ -163,12 +163,12 @@ export class DrFetch<T = unknown> {
163163
* Determines if processors are included in the clone. The default is `true`.
164164
*/
165165
includeProcessors?: boolean;
166-
}): TInherit extends true ? DrFetch<T> : DrFetch {
166+
}): TInherit extends true ? DrFetch<TStatusCode, T> : DrFetch {
167167
const newClone = new DrFetch(options?.fetchFn === false ? undefined : options?.fetchFn ?? this.#fetchFn);
168168
if (options?.includeProcessors ?? true) {
169169
newClone.#customProcessors = [...this.#customProcessors];
170170
}
171-
return newClone as DrFetch<T>;
171+
return newClone as DrFetch<TStatusCode, T>;
172172
}
173173

174174
/**
@@ -195,12 +195,10 @@ export class DrFetch<T = unknown> {
195195
* be a single status code, or multiple status codes.
196196
* @returns This fetcher object with its response type modified to include the body specification provided.
197197
*/
198-
for<TStatus extends StatusCode, TBody = {}>(): DrFetch<FetchResult<T, TStatus, TBody>> {
199-
return this as DrFetch<FetchResult<T, TStatus, TBody>>;
198+
for<TStatus extends TStatusCode, TBody = {}>(): DrFetch<TStatusCode, FetchResult<T, TStatus, TBody>> {
199+
return this as DrFetch<TStatusCode, FetchResult<T, TStatus, TBody>>;
200200
}
201201

202-
// notFor<TStatus extends StatusCode>(status: TStatus)
203-
204202
#contentMatchesType(contentType: string, types: (string | RegExp) | (string | RegExp)[]) {
205203
if (!Array.isArray(types)) {
206204
types = [types];
@@ -251,7 +249,7 @@ export class DrFetch<T = unknown> {
251249
/**
252250
* Fetches the specified URL using the specified options and returns information contained within the HTTP response
253251
* object.
254-
* @param url URL paramter for the data-fetching function.
252+
* @param url URL parameter for the data-fetching function.
255253
* @param init Options for the data-fetching function.
256254
* @returns A response object with the HTTP response's `ok`, `status`, `statusText` and `body` properties.
257255
*/

src/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export type BodyParserFn<T> = (response: Response) => Promise<T>;
3232
/**
3333
* Type that builds a single status code's response.
3434
*/
35-
type CoreFetchResult<TStatus extends StatusCode, TBody> = {
35+
type CoreFetchResult<TStatus extends number, TBody> = {
3636
ok: TStatus extends OkStatusCode ? true : false;
3737
status: TStatus;
3838
statusText: string;
@@ -43,7 +43,7 @@ type CoreFetchResult<TStatus extends StatusCode, TBody> = {
4343
/**
4444
* Type that builds DrFetch's final result object's type.
4545
*/
46-
export type FetchResult<T, TStatus extends StatusCode, TBody = undefined> =
46+
export type FetchResult<T, TStatus extends number, TBody = undefined> =
4747
(unknown extends T ? CoreFetchResult<TStatus, TBody> : T | CoreFetchResult<TStatus, TBody>) extends infer R ? R : never;
4848

4949
/**

0 commit comments

Comments
 (0)