Skip to content

Commit 43e3e69

Browse files
committed
Make reviver and parse dates options work together. Add new functional helpers. Update readme.
1 parent 72bb8e7 commit 43e3e69

File tree

6 files changed

+184
-33
lines changed

6 files changed

+184
-33
lines changed

deno.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
},
1212
"imports": {
1313
"@deno/dnt": "jsr:@deno/dnt@^0.41.3",
14-
"@std/assert": "jsr:@std/assert@^1.0.9",
15-
"@std/path": "jsr:@std/path@^1.0.8"
14+
"@std/assert": "jsr:@std/assert@^1.0.10",
15+
"@std/path": "jsr:@std/path@^1.0.8",
16+
"zod": "npm:zod@^3.24.1"
1617
},
1718
"exclude": ["npm"]
1819
}

deno.lock

Lines changed: 13 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

readme.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
FetchClient is a library that makes it easier to use the fetch API for JSON APIs. It provides the following features:
55

6-
* Makes fetch easier to use for JSON APIs
7-
* Automatic model validation
8-
* Caching
9-
* Middleware
10-
* Problem Details support
6+
* [Makes fetch easier to use for JSON APIs](#typed-response)
7+
* [Automatic model validation](#model-validator)
8+
* [Caching](#caching)
9+
* [Middleware](#middleware)
10+
* [Problem Details](https://www.rfc-editor.org/rfc/rfc9457.html) support
1111
* Option to parse dates in responses
1212

1313
## Install
@@ -22,7 +22,7 @@ npm install --save @exceptionless/fetchclient
2222

2323
## Usage
2424

25-
Get a typed JSON response:
25+
### Typed Response
2626

2727
```ts
2828
import { FetchClient } from '@exceptionless/fetchclient';
@@ -39,23 +39,23 @@ const response = await client.getJSON<Products>(
3939
const products = response.data;
4040
```
4141

42-
Get a typed JSON response using a function:
42+
### Typed Response Using a Function
4343

4444
```ts
45-
import { useFetchClient } from '@exceptionless/fetchclient';
45+
import { getJSON } from '@exceptionless/fetchclient';
4646

4747
type Products = {
4848
products: Array<{ id: number; name: string }>;
4949
};
5050

51-
const response = await useFetchClient().getJSON<Products>(
51+
const response = await getJSON<Products>(
5252
`https://dummyjson.com/products/search?q=iphone&limit=10`,
5353
);
5454

5555
const products = response.data;
5656
```
5757

58-
Use a model validator:
58+
### Model Validator
5959

6060
```ts
6161
import { FetchClient, setModelValidator } from '@exceptionless/fetchclient';
@@ -89,7 +89,7 @@ if (!response.ok) {
8989
}
9090
```
9191

92-
Use caching:
92+
### Caching
9393

9494
```ts
9595
import { FetchClient } from '@exceptionless/fetchclient';
@@ -109,7 +109,7 @@ const response = await client.getJSON<Todo>(
109109
client.cache.delete(["todos", "1"]);
110110
```
111111

112-
Use middleware:
112+
### Middleware
113113

114114
```ts
115115
import { FetchClient, useMiddleware } from '@exceptionless/fetchclient';
@@ -139,7 +139,7 @@ Also, take a look at the tests:
139139
Run tests:
140140

141141
```shell
142-
deno test --allow-net
142+
deno run test
143143
```
144144

145145
Lint code:
@@ -157,7 +157,7 @@ deno fmt
157157
Type check code:
158158

159159
```shell
160-
deno check scripts/*.ts *.ts src/*.ts
160+
deno run check
161161
```
162162

163163
## License

src/DefaultHelpers.ts

Lines changed: 82 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,99 @@ import {
55
defaultInstance as defaultProvider,
66
type FetchClientProvider,
77
} from "./FetchClientProvider.ts";
8+
import type { FetchClientResponse } from "./FetchClientResponse.ts";
89
import type { ProblemDetails } from "./ProblemDetails.ts";
9-
import type { RequestOptions } from "./RequestOptions.ts";
10+
import type { GetRequestOptions, RequestOptions } from "./RequestOptions.ts";
1011

1112
let getCurrentProviderFunc: () => FetchClientProvider | null = () => null;
1213

1314
/**
14-
* Gets a FetchClient instance.
15+
* Gets a FetchClient instance from the current provider.
1516
* @returns The FetchClient instance.
1617
*/
1718
export function useFetchClient(options?: FetchClientOptions): FetchClient {
1819
return getCurrentProvider().getFetchClient(options);
1920
}
2021

22+
/**
23+
* Sends a GET request to the specified URL using the default client and provider and returns the response as JSON.
24+
* @param url - The URL to send the GET request to.
25+
* @param options - Optional request options.
26+
* @returns A promise that resolves to the response as JSON.
27+
*/
28+
export function getJSON<T>(
29+
url: string,
30+
options?: GetRequestOptions,
31+
): Promise<FetchClientResponse<T>> {
32+
return useFetchClient().getJSON(url, options);
33+
}
34+
35+
/**
36+
* Sends a POST request with JSON payload using the default client and provider to the specified URL.
37+
*
38+
* @template T - The type of the response data.
39+
* @param {string} url - The URL to send the request to.
40+
* @param {object | string | FormData} [body] - The JSON payload or form data to send with the request.
41+
* @param {RequestOptions} [options] - Additional options for the request.
42+
* @returns {Promise<FetchClientResponse<T>>} - A promise that resolves to the response data.
43+
*/
44+
export function postJSON<T>(
45+
url: string,
46+
body?: object | string | FormData,
47+
options?: RequestOptions,
48+
): Promise<FetchClientResponse<T>> {
49+
return useFetchClient().postJSON(url, body, options);
50+
}
51+
52+
/**
53+
* Sends a PUT request with JSON payload using the default client and provider to the specified URL.
54+
*
55+
* @template T - The type of the response data.
56+
* @param {string} url - The URL to send the request to.
57+
* @param {object | string} [body] - The JSON payload to send with the request.
58+
* @param {RequestOptions} [options] - Additional options for the request.
59+
* @returns {Promise<FetchClientResponse<T>>} - A promise that resolves to the response data.
60+
*/
61+
export function putJSON<T>(
62+
url: string,
63+
body?: object | string,
64+
options?: RequestOptions,
65+
): Promise<FetchClientResponse<T>> {
66+
return useFetchClient().putJSON(url, body, options);
67+
}
68+
69+
/**
70+
* Sends a PATCH request with JSON payload using the default client and provider to the specified URL.
71+
*
72+
* @template T - The type of the response data.
73+
* @param {string} url - The URL to send the request to.
74+
* @param {object | string} [body] - The JSON payload to send with the request.
75+
* @param {RequestOptions} [options] - Additional options for the request.
76+
* @returns {Promise<FetchClientResponse<T>>} - A promise that resolves to the response data.
77+
*/
78+
export function patchJSON<T>(
79+
url: string,
80+
body?: object | string,
81+
options?: RequestOptions,
82+
): Promise<FetchClientResponse<T>> {
83+
return useFetchClient().patchJSON(url, body, options);
84+
}
85+
86+
/**
87+
* Sends a DELETE request with JSON payload using the default client and provider to the specified URL.
88+
*
89+
* @template T - The type of the response data.
90+
* @param {string} url - The URL to send the request to.
91+
* @param {RequestOptions} [options] - Additional options for the request.
92+
* @returns {Promise<FetchClientResponse<T>>} - A promise that resolves to the response data.
93+
*/
94+
export function deleteJSON<T>(
95+
url: string,
96+
options?: RequestOptions,
97+
): Promise<FetchClientResponse<T>> {
98+
return useFetchClient().deleteJSON(url, options);
99+
}
100+
21101
/**
22102
* Gets the current FetchClientProvider.
23103
* @returns The current FetchClientProvider.

src/FetchClient.test.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@ import { assert, assertEquals, assertFalse, assertRejects } from "@std/assert";
22
import {
33
FetchClient,
44
type FetchClientContext,
5+
getJSON,
56
ProblemDetails,
67
setBaseUrl,
78
useFetchClient,
89
} from "../mod.ts";
910
import { FetchClientProvider } from "./FetchClientProvider.ts";
10-
import { z, type ZodTypeAny } from "https://deno.land/x/zod@v3.23.8/mod.ts";
11+
import { z, type ZodTypeAny } from "zod";
1112

1213
export const TodoSchema = z.object({
1314
userId: z.number(),
@@ -32,7 +33,7 @@ Deno.test("can getJSON", async () => {
3233
});
3334

3435
Deno.test("can use function", async () => {
35-
const res = await useFetchClient().getJSON<Products>(
36+
const res = await getJSON<Products>(
3637
`https://dummyjson.com/products/search?q=iphone&limit=10`,
3738
);
3839

@@ -662,6 +663,52 @@ Deno.test("can use reviver", async () => {
662663
assert(res.data.completedTime instanceof Date);
663664
});
664665

666+
Deno.test("can parse dates and use reviver", async () => {
667+
const provider = new FetchClientProvider();
668+
const fakeFetch = (): Promise<Response> =>
669+
new Promise((resolve) => {
670+
const data = JSON.stringify({
671+
userId: 1,
672+
id: 1,
673+
title: "A random title",
674+
completed: false,
675+
completedTime: "2021-01-01T00:00:00.000Z",
676+
});
677+
resolve(new Response(data));
678+
});
679+
680+
provider.fetch = fakeFetch;
681+
682+
const api = provider.getFetchClient();
683+
684+
let res = await api.getJSON<Todo>(
685+
`https://jsonplaceholder.typicode.com/todos/1`,
686+
);
687+
688+
assertEquals(res.status, 200);
689+
assert(res.data);
690+
assertEquals(res.data.title, "A random title");
691+
assertFalse(res.data.completedTime instanceof Date);
692+
693+
res = await api.getJSON<Todo>(
694+
`https://jsonplaceholder.typicode.com/todos/1`,
695+
{
696+
shouldParseDates: true,
697+
reviver: (key: string, value: unknown) => {
698+
if (key === "title") {
699+
return "revived";
700+
}
701+
return value;
702+
},
703+
},
704+
);
705+
706+
assertEquals(res.status, 200);
707+
assert(res.data);
708+
assertEquals(res.data.title, "revived");
709+
assert(res.data.completedTime instanceof Date);
710+
});
711+
665712
Deno.test("can use kitchen sink", async () => {
666713
let called = false;
667714
let optionsCalled = false;

src/FetchClient.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -520,13 +520,11 @@ export class FetchClient {
520520
): Promise<FetchClientResponse<T>> {
521521
let data = null;
522522
try {
523-
if (options.reviver) {
523+
if (options.reviver || options.shouldParseDates) {
524524
const body = await response.text();
525-
data = JSON.parse(body, options.reviver);
526-
} else if (options.shouldParseDates) {
527-
// TODO: Combine reviver and shouldParseDates into a single function
528-
const body = await response.text();
529-
data = JSON.parse(body, this.parseDates);
525+
data = JSON.parse(body, (key, value) => {
526+
return this.reviveJsonValue(options, key, value);
527+
});
530528
} else {
531529
data = await response.json();
532530
}
@@ -554,7 +552,25 @@ export class FetchClient {
554552
return jsonResponse;
555553
}
556554

557-
private parseDates(this: unknown, _key: string, value: unknown): unknown {
555+
private reviveJsonValue(
556+
options: RequestOptions,
557+
key: string,
558+
value: unknown,
559+
): unknown {
560+
let revivedValued = value;
561+
562+
if (options.reviver) {
563+
revivedValued = options.reviver.call(this, key, revivedValued);
564+
}
565+
566+
if (options.shouldParseDates) {
567+
revivedValued = this.tryParseDate(key, revivedValued);
568+
}
569+
570+
return revivedValued;
571+
}
572+
573+
private tryParseDate(_key: string, value: unknown): unknown {
558574
if (typeof value !== "string") {
559575
return value;
560576
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy