@foundatiofx/fetchclient 1.1.0 → 1.2.0

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.
Files changed (45) hide show
  1. package/esm/mod.js +83 -0
  2. package/esm/src/DefaultHelpers.js +12 -5
  3. package/esm/src/FetchClient.js +66 -41
  4. package/esm/src/FetchClientError.js +73 -0
  5. package/esm/src/FetchClientProvider.js +24 -0
  6. package/esm/src/RateLimitMiddleware.js +35 -1
  7. package/esm/src/ResponsePromise.js +163 -0
  8. package/esm/src/RetryMiddleware.js +179 -0
  9. package/esm/src/mocks/MockHistory.js +8 -0
  10. package/esm/src/mocks/MockRegistry.js +7 -0
  11. package/package.json +1 -1
  12. package/readme.md +8 -15
  13. package/script/mod.js +94 -1
  14. package/script/src/DefaultHelpers.js +13 -5
  15. package/script/src/FetchClient.js +66 -41
  16. package/script/src/FetchClientError.js +77 -0
  17. package/script/src/FetchClientProvider.js +24 -0
  18. package/script/src/RateLimitMiddleware.js +36 -0
  19. package/script/src/ResponsePromise.js +167 -0
  20. package/script/src/RetryMiddleware.js +184 -0
  21. package/script/src/mocks/MockHistory.js +8 -0
  22. package/script/src/mocks/MockRegistry.js +7 -0
  23. package/types/mod.d.ts +96 -0
  24. package/types/mod.d.ts.map +1 -1
  25. package/types/src/DefaultHelpers.d.ts +11 -5
  26. package/types/src/DefaultHelpers.d.ts.map +1 -1
  27. package/types/src/FetchClient.d.ts +31 -15
  28. package/types/src/FetchClient.d.ts.map +1 -1
  29. package/types/src/FetchClientError.d.ts +29 -0
  30. package/types/src/FetchClientError.d.ts.map +1 -0
  31. package/types/src/FetchClientProvider.d.ts +11 -0
  32. package/types/src/FetchClientProvider.d.ts.map +1 -1
  33. package/types/src/RateLimitMiddleware.d.ts +27 -0
  34. package/types/src/RateLimitMiddleware.d.ts.map +1 -1
  35. package/types/src/ResponsePromise.d.ts +93 -0
  36. package/types/src/ResponsePromise.d.ts.map +1 -0
  37. package/types/src/RetryMiddleware.d.ts +88 -0
  38. package/types/src/RetryMiddleware.d.ts.map +1 -0
  39. package/types/src/mocks/MockHistory.d.ts +1 -0
  40. package/types/src/mocks/MockHistory.d.ts.map +1 -1
  41. package/types/src/mocks/MockRegistry.d.ts +5 -0
  42. package/types/src/mocks/MockRegistry.d.ts.map +1 -1
  43. package/types/src/mocks/types.d.ts +2 -0
  44. package/types/src/mocks/types.d.ts.map +1 -1
  45. package/types/src/tests/RetryMiddleware.test.d.ts.map +1 -0
@@ -7,6 +7,8 @@ const LinkHeader_js_1 = require("./LinkHeader.js");
7
7
  const FetchClientProvider_js_1 = require("./FetchClientProvider.js");
8
8
  const DefaultHelpers_js_1 = require("./DefaultHelpers.js");
9
9
  const ObjectEvent_js_1 = require("./ObjectEvent.js");
10
+ const ResponsePromise_js_1 = require("./ResponsePromise.js");
11
+ const FetchClientError_js_1 = require("./FetchClientError.js");
10
12
  /**
11
13
  * Represents a client for making HTTP requests using the Fetch API.
12
14
  */
@@ -103,24 +105,27 @@ class FetchClient {
103
105
  *
104
106
  * @param url - The URL to send the GET request to.
105
107
  * @param options - The optional request options.
106
- * @returns A promise that resolves to the response of the GET request.
108
+ * @returns A ResponsePromise that resolves to the response. Can use `.json<T>()` for typed JSON.
107
109
  */
108
- async get(url, options) {
109
- options = {
110
+ get(url, options) {
111
+ const mergedOptions = {
110
112
  ...this.options.defaultRequestOptions,
111
113
  ...options,
112
114
  };
113
- const response = await this.fetchInternal(url, options, this.buildRequestInit("GET", undefined, options));
114
- return response;
115
+ const responsePromise = this.fetchInternal(url, mergedOptions, this.buildRequestInit("GET", undefined, mergedOptions));
116
+ return new ResponsePromise_js_1.ResponsePromise(responsePromise, mergedOptions);
115
117
  }
116
118
  /**
117
119
  * Sends a GET request to the specified URL and returns the response as JSON.
120
+ * The response will have the parsed JSON in `response.data`.
121
+ *
118
122
  * @param url - The URL to send the GET request to.
119
123
  * @param options - Optional request options.
120
- * @returns A promise that resolves to the response as JSON.
124
+ * @returns A promise that resolves to the response with parsed JSON in `data`.
121
125
  */
122
- getJSON(url, options) {
123
- return this.get(url, this.buildJsonRequestOptions(options));
126
+ async getJSON(url, options) {
127
+ const mergedOptions = this.buildJsonRequestOptions(options);
128
+ return await this.get(url, mergedOptions);
124
129
  }
125
130
  /**
126
131
  * Sends a POST request to the specified URL.
@@ -128,107 +133,127 @@ class FetchClient {
128
133
  * @param url - The URL to send the request to.
129
134
  * @param body - The request body, can be an object, a string, or FormData.
130
135
  * @param options - Additional options for the request.
131
- * @returns A promise that resolves to a FetchClientResponse object.
136
+ * @returns A ResponsePromise that resolves to the response. Can use `.json<T>()` for typed JSON.
132
137
  */
133
- async post(url, body, options) {
134
- options = {
138
+ post(url, body, options) {
139
+ const mergedOptions = {
135
140
  ...this.options.defaultRequestOptions,
136
141
  ...options,
137
142
  };
138
- const response = await this.fetchInternal(url, options, this.buildRequestInit("POST", body, options));
139
- return response;
143
+ const responsePromise = this.fetchInternal(url, mergedOptions, this.buildRequestInit("POST", body, mergedOptions));
144
+ return new ResponsePromise_js_1.ResponsePromise(responsePromise, mergedOptions);
140
145
  }
141
146
  /**
142
147
  * Sends a POST request with JSON payload to the specified URL.
148
+ * The response will have the parsed JSON in `response.data`.
143
149
  *
144
150
  * @template T - The type of the response data.
145
151
  * @param {string} url - The URL to send the request to.
146
152
  * @param {object | string | FormData} [body] - The JSON payload or form data to send with the request.
147
153
  * @param {RequestOptions} [options] - Additional options for the request.
148
- * @returns {Promise<FetchClientResponse<T>>} - A promise that resolves to the response data.
154
+ * @returns A promise that resolves to the response with parsed JSON in `data`.
149
155
  */
150
- postJSON(url, body, options) {
151
- return this.post(url, body, this.buildJsonRequestOptions(options));
156
+ async postJSON(url, body, options) {
157
+ return await this.post(url, body, this.buildJsonRequestOptions(options));
152
158
  }
153
159
  /**
154
160
  * Sends a PUT request to the specified URL with the given body and options.
155
161
  * @param url - The URL to send the request to.
156
162
  * @param body - The request body, can be an object, a string, or FormData.
157
163
  * @param options - The request options.
158
- * @returns A promise that resolves to a FetchClientResponse object.
164
+ * @returns A ResponsePromise that resolves to the response. Can use `.json<T>()` for typed JSON.
159
165
  */
160
- async put(url, body, options) {
161
- options = {
166
+ put(url, body, options) {
167
+ const mergedOptions = {
162
168
  ...this.options.defaultRequestOptions,
163
169
  ...options,
164
170
  };
165
- const response = await this.fetchInternal(url, options, this.buildRequestInit("PUT", body, options));
166
- return response;
171
+ const responsePromise = this.fetchInternal(url, mergedOptions, this.buildRequestInit("PUT", body, mergedOptions));
172
+ return new ResponsePromise_js_1.ResponsePromise(responsePromise, mergedOptions);
167
173
  }
168
174
  /**
169
175
  * Sends a PUT request with JSON payload to the specified URL.
176
+ * The response will have the parsed JSON in `response.data`.
170
177
  *
171
178
  * @template T - The type of the response data.
172
179
  * @param {string} url - The URL to send the request to.
173
180
  * @param {object | string} [body] - The JSON payload to send with the request.
174
181
  * @param {RequestOptions} [options] - Additional options for the request.
175
- * @returns {Promise<FetchClientResponse<T>>} - A promise that resolves to the response data.
182
+ * @returns A promise that resolves to the response with parsed JSON in `data`.
176
183
  */
177
- putJSON(url, body, options) {
178
- return this.put(url, body, this.buildJsonRequestOptions(options));
184
+ async putJSON(url, body, options) {
185
+ return await this.put(url, body, this.buildJsonRequestOptions(options));
179
186
  }
180
187
  /**
181
188
  * Sends a PATCH request to the specified URL with the provided body and options.
182
189
  * @param url - The URL to send the PATCH request to.
183
190
  * @param body - The body of the request. It can be an object, a string, or FormData.
184
191
  * @param options - The options for the request.
185
- * @returns A Promise that resolves to the response of the PATCH request.
192
+ * @returns A ResponsePromise that resolves to the response. Can use `.json<T>()` for typed JSON.
186
193
  */
187
- async patch(url, body, options) {
188
- options = {
194
+ patch(url, body, options) {
195
+ const mergedOptions = {
189
196
  ...this.options.defaultRequestOptions,
190
197
  ...options,
191
198
  };
192
- const response = await this.fetchInternal(url, options, this.buildRequestInit("PATCH", body, options));
193
- return response;
199
+ const responsePromise = this.fetchInternal(url, mergedOptions, this.buildRequestInit("PATCH", body, mergedOptions));
200
+ return new ResponsePromise_js_1.ResponsePromise(responsePromise, mergedOptions);
194
201
  }
195
202
  /**
196
203
  * Sends a PATCH request with JSON payload to the specified URL.
204
+ * The response will have the parsed JSON in `response.data`.
197
205
  *
198
206
  * @template T - The type of the response data.
199
207
  * @param {string} url - The URL to send the request to.
200
208
  * @param {object | string} [body] - The JSON payload to send with the request.
201
209
  * @param {RequestOptions} [options] - Additional options for the request.
202
- * @returns {Promise<FetchClientResponse<T>>} - A promise that resolves to the response data.
210
+ * @returns A promise that resolves to the response with parsed JSON in `data`.
203
211
  */
204
- patchJSON(url, body, options) {
205
- return this.patch(url, body, this.buildJsonRequestOptions(options));
212
+ async patchJSON(url, body, options) {
213
+ return await this.patch(url, body, this.buildJsonRequestOptions(options));
206
214
  }
207
215
  /**
208
216
  * Sends a DELETE request to the specified URL.
209
217
  *
210
218
  * @param url - The URL to send the DELETE request to.
211
219
  * @param options - The options for the request.
212
- * @returns A promise that resolves to a `FetchClientResponse` object.
220
+ * @returns A ResponsePromise that resolves to the response. Can use `.json<T>()` for typed JSON.
213
221
  */
214
- async delete(url, options) {
215
- options = {
222
+ delete(url, options) {
223
+ const mergedOptions = {
216
224
  ...this.options.defaultRequestOptions,
217
225
  ...options,
218
226
  };
219
- const response = await this.fetchInternal(url, options, this.buildRequestInit("DELETE", undefined, options));
220
- return response;
227
+ const responsePromise = this.fetchInternal(url, mergedOptions, this.buildRequestInit("DELETE", undefined, mergedOptions));
228
+ return new ResponsePromise_js_1.ResponsePromise(responsePromise, mergedOptions);
221
229
  }
222
230
  /**
223
231
  * Sends a DELETE request with JSON payload to the specified URL.
232
+ * The response will have the parsed JSON in `response.data`.
224
233
  *
225
234
  * @template T - The type of the response data.
226
235
  * @param {string} url - The URL to send the request to.
227
236
  * @param {RequestOptions} [options] - Additional options for the request.
228
- * @returns {Promise<FetchClientResponse<T>>} - A promise that resolves to the response data.
237
+ * @returns A promise that resolves to the response with parsed JSON in `data`.
229
238
  */
230
- deleteJSON(url, options) {
231
- return this.delete(url, this.buildJsonRequestOptions(options));
239
+ async deleteJSON(url, options) {
240
+ return await this.delete(url, this.buildJsonRequestOptions(options));
241
+ }
242
+ /**
243
+ * Sends a HEAD request to the specified URL.
244
+ * HEAD requests are identical to GET requests but without the response body.
245
+ *
246
+ * @param url - The URL to send the HEAD request to.
247
+ * @param options - The optional request options.
248
+ * @returns A ResponsePromise that resolves to the response.
249
+ */
250
+ head(url, options) {
251
+ const mergedOptions = {
252
+ ...this.options.defaultRequestOptions,
253
+ ...options,
254
+ };
255
+ const responsePromise = this.fetchInternal(url, mergedOptions, this.buildRequestInit("HEAD", undefined, mergedOptions));
256
+ return new ResponsePromise_js_1.ResponsePromise(responsePromise, mergedOptions);
232
257
  }
233
258
  async validate(data, options) {
234
259
  if (typeof data !== "object" ||
@@ -543,7 +568,7 @@ class FetchClient {
543
568
  response.problem.status = response.status;
544
569
  response.problem.title = `Unexpected status code: ${response.status}`;
545
570
  response.problem.setErrorMessage(response.problem.title);
546
- throw response;
571
+ throw new FetchClientError_js_1.FetchClientError(response);
547
572
  }
548
573
  }
549
574
  exports.FetchClient = FetchClient;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FetchClientError = void 0;
4
+ /**
5
+ * Error wrapper for non-2xx responses.
6
+ * Exposes the underlying response for compatibility and debugging.
7
+ */
8
+ class FetchClientError extends Error {
9
+ response;
10
+ constructor(response, message) {
11
+ super(message ??
12
+ response.problem?.title ??
13
+ `Unexpected status code: ${response.status}`);
14
+ this.name = "FetchClientError";
15
+ this.response = response;
16
+ }
17
+ get status() {
18
+ return this.response.status;
19
+ }
20
+ get statusText() {
21
+ return this.response.statusText;
22
+ }
23
+ get ok() {
24
+ return this.response.ok;
25
+ }
26
+ get headers() {
27
+ return this.response.headers;
28
+ }
29
+ get url() {
30
+ return this.response.url;
31
+ }
32
+ get redirected() {
33
+ return this.response.redirected;
34
+ }
35
+ get type() {
36
+ return this.response.type;
37
+ }
38
+ get body() {
39
+ return this.response.body;
40
+ }
41
+ get bodyUsed() {
42
+ return this.response.bodyUsed;
43
+ }
44
+ get data() {
45
+ return this.response.data;
46
+ }
47
+ get problem() {
48
+ return this.response.problem;
49
+ }
50
+ get meta() {
51
+ return this.response.meta;
52
+ }
53
+ json() {
54
+ return this.response.json();
55
+ }
56
+ text() {
57
+ return this.response.text();
58
+ }
59
+ arrayBuffer() {
60
+ return this.response.arrayBuffer();
61
+ }
62
+ blob() {
63
+ return this.response.blob();
64
+ }
65
+ formData() {
66
+ return this.response.formData();
67
+ }
68
+ // @ts-ignore: New in Deno 1.44
69
+ bytes() {
70
+ // @ts-ignore: New in Deno 1.44
71
+ return this.response.bytes();
72
+ }
73
+ clone() {
74
+ return this.response.clone();
75
+ }
76
+ }
77
+ exports.FetchClientError = FetchClientError;
@@ -9,6 +9,7 @@ const RateLimitMiddleware_js_1 = require("./RateLimitMiddleware.js");
9
9
  const RateLimiter_js_1 = require("./RateLimiter.js");
10
10
  const CircuitBreakerMiddleware_js_1 = require("./CircuitBreakerMiddleware.js");
11
11
  const CircuitBreaker_js_1 = require("./CircuitBreaker.js");
12
+ const RetryMiddleware_js_1 = require("./RetryMiddleware.js");
12
13
  /**
13
14
  * Represents a provider for creating instances of the FetchClient class with shared default options and cache.
14
15
  */
@@ -20,6 +21,8 @@ class FetchClientProvider {
20
21
  #rateLimitMiddlewareFunc;
21
22
  #circuitBreakerMiddleware;
22
23
  #circuitBreakerMiddlewareFunc;
24
+ #retryMiddleware;
25
+ #retryMiddlewareFunc;
23
26
  #counter = new Counter_js_1.Counter();
24
27
  #onLoading = new ObjectEvent_js_1.ObjectEvent();
25
28
  /**
@@ -253,6 +256,27 @@ class FetchClientProvider {
253
256
  this.#options.middleware = this.#options.middleware?.filter((m) => m !== middlewareFunc);
254
257
  }
255
258
  }
259
+ /**
260
+ * Enables automatic retry for failed requests.
261
+ * Retries are performed with exponential backoff and jitter.
262
+ * @param options - The retry configuration options.
263
+ */
264
+ useRetry(options) {
265
+ this.#retryMiddleware = new RetryMiddleware_js_1.RetryMiddleware(options);
266
+ this.#retryMiddlewareFunc = this.#retryMiddleware.middleware();
267
+ this.useMiddleware(this.#retryMiddlewareFunc);
268
+ }
269
+ /**
270
+ * Removes the retry middleware from all FetchClient instances created by this provider.
271
+ */
272
+ removeRetry() {
273
+ const middlewareFunc = this.#retryMiddlewareFunc;
274
+ this.#retryMiddleware = undefined;
275
+ this.#retryMiddlewareFunc = undefined;
276
+ if (middlewareFunc) {
277
+ this.#options.middleware = this.#options.middleware?.filter((m) => m !== middlewareFunc);
278
+ }
279
+ }
256
280
  }
257
281
  exports.FetchClientProvider = FetchClientProvider;
258
282
  const provider = new FetchClientProvider();
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RateLimitMiddleware = exports.RateLimitError = void 0;
4
+ exports.createRateLimitMiddleware = createRateLimitMiddleware;
5
+ exports.createPerDomainRateLimitMiddleware = createPerDomainRateLimitMiddleware;
4
6
  const ProblemDetails_js_1 = require("./ProblemDetails.js");
5
7
  const RateLimiter_js_1 = require("./RateLimiter.js");
6
8
  /**
@@ -118,3 +120,37 @@ class RateLimitMiddleware {
118
120
  }
119
121
  }
120
122
  exports.RateLimitMiddleware = RateLimitMiddleware;
123
+ /**
124
+ * Creates a rate limit middleware with the given options.
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * const client = new FetchClient();
129
+ * client.use(createRateLimitMiddleware({
130
+ * maxRequests: 100,
131
+ * windowSeconds: 60,
132
+ * }));
133
+ * ```
134
+ */
135
+ function createRateLimitMiddleware(options) {
136
+ return new RateLimitMiddleware(options).middleware();
137
+ }
138
+ /**
139
+ * Creates a per-domain rate limit middleware where each domain is tracked separately.
140
+ *
141
+ * @example
142
+ * ```typescript
143
+ * const client = new FetchClient();
144
+ * client.use(createPerDomainRateLimitMiddleware({
145
+ * maxRequests: 100,
146
+ * windowSeconds: 60,
147
+ * }));
148
+ * // api.example.com and api.other.com will have separate rate limits
149
+ * ```
150
+ */
151
+ function createPerDomainRateLimitMiddleware(options) {
152
+ return new RateLimitMiddleware({
153
+ ...options,
154
+ getGroupFunc: RateLimiter_js_1.groupByDomain,
155
+ }).middleware();
156
+ }
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResponsePromise = void 0;
4
+ /**
5
+ * A promise that resolves to a FetchClientResponse with additional helper methods
6
+ * for parsing the response body. This allows for a fluent API similar to ky:
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * // Await to get the full response
11
+ * const response = await client.get("/api/users");
12
+ *
13
+ * // Or use helper methods for direct access to parsed body
14
+ * const users = await client.get("/api/users").json<User[]>();
15
+ * const html = await client.get("/page").text();
16
+ * const file = await client.get("/file").blob();
17
+ * ```
18
+ */
19
+ class ResponsePromise {
20
+ #responsePromise;
21
+ #options;
22
+ constructor(responsePromise, options) {
23
+ this.#responsePromise = responsePromise;
24
+ this.#options = options;
25
+ }
26
+ /**
27
+ * Implements PromiseLike interface so the ResponsePromise can be awaited.
28
+ */
29
+ then(onfulfilled, onrejected) {
30
+ return this.#responsePromise.then(onfulfilled, onrejected);
31
+ }
32
+ /**
33
+ * Catches any errors from the response promise.
34
+ */
35
+ catch(onrejected) {
36
+ return this.#responsePromise.catch(onrejected);
37
+ }
38
+ /**
39
+ * Executes a callback when the promise settles (fulfilled or rejected).
40
+ */
41
+ finally(onfinally) {
42
+ return this.#responsePromise.finally(onfinally);
43
+ }
44
+ /**
45
+ * Parses the response body as JSON.
46
+ *
47
+ * If the response was already parsed as JSON (via getJSON, postJSON, etc.),
48
+ * returns the parsed data directly. Otherwise, parses the response body.
49
+ *
50
+ * @template TJson - The expected type of the JSON response
51
+ * @returns A promise that resolves to the parsed JSON
52
+ *
53
+ * @example
54
+ * ```typescript
55
+ * const user = await client.get("/api/user/1").json<User>();
56
+ * ```
57
+ */
58
+ async json() {
59
+ const response = await this.#responsePromise;
60
+ // If the response already has parsed data (from getJSON, etc.), return it
61
+ if (response.data !== null && response.data !== undefined) {
62
+ return response.data;
63
+ }
64
+ // Otherwise, parse the response body as JSON
65
+ const data = await response.json();
66
+ // Apply reviver and date parsing if options are set
67
+ if (this.#options?.reviver || this.#options?.shouldParseDates) {
68
+ return this.#reviveJson(data);
69
+ }
70
+ return data;
71
+ }
72
+ /**
73
+ * Returns the response body as text.
74
+ *
75
+ * @returns A promise that resolves to the response text
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * const html = await client.get("/page").text();
80
+ * ```
81
+ */
82
+ async text() {
83
+ const response = await this.#responsePromise;
84
+ return response.text();
85
+ }
86
+ /**
87
+ * Returns the response body as a Blob.
88
+ *
89
+ * @returns A promise that resolves to the response as a Blob
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * const imageBlob = await client.get("/image.png").blob();
94
+ * ```
95
+ */
96
+ async blob() {
97
+ const response = await this.#responsePromise;
98
+ return response.blob();
99
+ }
100
+ /**
101
+ * Returns the response body as an ArrayBuffer.
102
+ *
103
+ * @returns A promise that resolves to the response as an ArrayBuffer
104
+ *
105
+ * @example
106
+ * ```typescript
107
+ * const buffer = await client.get("/file").arrayBuffer();
108
+ * ```
109
+ */
110
+ async arrayBuffer() {
111
+ const response = await this.#responsePromise;
112
+ return response.arrayBuffer();
113
+ }
114
+ /**
115
+ * Returns the response body as FormData.
116
+ *
117
+ * @returns A promise that resolves to the response as FormData
118
+ *
119
+ * @example
120
+ * ```typescript
121
+ * const formData = await client.get("/form").formData();
122
+ * ```
123
+ */
124
+ async formData() {
125
+ const response = await this.#responsePromise;
126
+ return response.formData();
127
+ }
128
+ #reviveJson(data) {
129
+ if (data === null || data === undefined) {
130
+ return data;
131
+ }
132
+ if (Array.isArray(data)) {
133
+ return data.map((item) => this.#reviveJson(item));
134
+ }
135
+ if (typeof data === "object") {
136
+ const result = {};
137
+ for (const [key, value] of Object.entries(data)) {
138
+ result[key] = this.#reviveValue(key, this.#reviveJson(value));
139
+ }
140
+ return result;
141
+ }
142
+ return this.#reviveValue("", data);
143
+ }
144
+ #reviveValue(key, value) {
145
+ let revivedValue = value;
146
+ if (this.#options?.reviver) {
147
+ revivedValue = this.#options.reviver.call(this, key, revivedValue);
148
+ }
149
+ if (this.#options?.shouldParseDates) {
150
+ revivedValue = this.#tryParseDate(revivedValue);
151
+ }
152
+ return revivedValue;
153
+ }
154
+ #tryParseDate(value) {
155
+ if (typeof value !== "string") {
156
+ return value;
157
+ }
158
+ if (/^\d{4}-\d{2}-\d{2}/.test(value)) {
159
+ const date = new Date(value);
160
+ if (!isNaN(date.getTime())) {
161
+ return date;
162
+ }
163
+ }
164
+ return value;
165
+ }
166
+ }
167
+ exports.ResponsePromise = ResponsePromise;