@foundatiofx/fetchclient 1.1.1 → 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.
- package/esm/mod.js +83 -0
- package/esm/src/DefaultHelpers.js +5 -5
- package/esm/src/FetchClient.js +66 -41
- package/esm/src/FetchClientError.js +73 -0
- package/esm/src/FetchClientProvider.js +24 -0
- package/esm/src/RateLimitMiddleware.js +35 -1
- package/esm/src/ResponsePromise.js +163 -0
- package/esm/src/RetryMiddleware.js +179 -0
- package/esm/src/mocks/MockHistory.js +8 -0
- package/esm/src/mocks/MockRegistry.js +7 -0
- package/package.json +1 -1
- package/script/mod.js +94 -1
- package/script/src/DefaultHelpers.js +5 -5
- package/script/src/FetchClient.js +66 -41
- package/script/src/FetchClientError.js +77 -0
- package/script/src/FetchClientProvider.js +24 -0
- package/script/src/RateLimitMiddleware.js +36 -0
- package/script/src/ResponsePromise.js +167 -0
- package/script/src/RetryMiddleware.js +184 -0
- package/script/src/mocks/MockHistory.js +8 -0
- package/script/src/mocks/MockRegistry.js +7 -0
- package/types/mod.d.ts +96 -0
- package/types/mod.d.ts.map +1 -1
- package/types/src/DefaultHelpers.d.ts +5 -5
- package/types/src/FetchClient.d.ts +31 -15
- package/types/src/FetchClient.d.ts.map +1 -1
- package/types/src/FetchClientError.d.ts +29 -0
- package/types/src/FetchClientError.d.ts.map +1 -0
- package/types/src/FetchClientProvider.d.ts +11 -0
- package/types/src/FetchClientProvider.d.ts.map +1 -1
- package/types/src/RateLimitMiddleware.d.ts +27 -0
- package/types/src/RateLimitMiddleware.d.ts.map +1 -1
- package/types/src/ResponsePromise.d.ts +93 -0
- package/types/src/ResponsePromise.d.ts.map +1 -0
- package/types/src/RetryMiddleware.d.ts +88 -0
- package/types/src/RetryMiddleware.d.ts.map +1 -0
- package/types/src/mocks/MockHistory.d.ts +1 -0
- package/types/src/mocks/MockHistory.d.ts.map +1 -1
- package/types/src/mocks/MockRegistry.d.ts +5 -0
- package/types/src/mocks/MockRegistry.d.ts.map +1 -1
- package/types/src/mocks/types.d.ts +2 -0
- package/types/src/mocks/types.d.ts.map +1 -1
- package/types/src/tests/RetryMiddleware.test.d.ts.map +1 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default HTTP methods that are eligible for retry.
|
|
3
|
+
* These are idempotent methods that can be safely retried without side effects.
|
|
4
|
+
*/
|
|
5
|
+
const DEFAULT_RETRY_METHODS = [
|
|
6
|
+
"GET",
|
|
7
|
+
"HEAD",
|
|
8
|
+
"PUT",
|
|
9
|
+
"DELETE",
|
|
10
|
+
"OPTIONS",
|
|
11
|
+
"TRACE",
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Default HTTP status codes that trigger a retry.
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_RETRY_STATUS_CODES = [
|
|
17
|
+
408, // Request Timeout
|
|
18
|
+
413, // Payload Too Large (rate limiting)
|
|
19
|
+
429, // Too Many Requests
|
|
20
|
+
500, // Internal Server Error
|
|
21
|
+
502, // Bad Gateway
|
|
22
|
+
503, // Service Unavailable
|
|
23
|
+
504, // Gateway Timeout
|
|
24
|
+
];
|
|
25
|
+
/**
|
|
26
|
+
* Retry middleware that automatically retries failed requests with exponential backoff.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const provider = new FetchClientProvider();
|
|
31
|
+
* provider.useRetry({
|
|
32
|
+
* limit: 3,
|
|
33
|
+
* statusCodes: [500, 502, 503, 504],
|
|
34
|
+
* jitter: 0.1,
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* const client = provider.getFetchClient();
|
|
38
|
+
* const response = await client.getJSON('/api/data');
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export class RetryMiddleware {
|
|
42
|
+
#options;
|
|
43
|
+
constructor(options) {
|
|
44
|
+
this.#options = {
|
|
45
|
+
limit: options?.limit ?? 2,
|
|
46
|
+
methods: (options?.methods ?? DEFAULT_RETRY_METHODS).map((m) => m.toUpperCase()),
|
|
47
|
+
statusCodes: options?.statusCodes ?? DEFAULT_RETRY_STATUS_CODES,
|
|
48
|
+
maxRetryAfter: options?.maxRetryAfter ?? Infinity,
|
|
49
|
+
backoffLimit: options?.backoffLimit ?? 30000,
|
|
50
|
+
jitter: options?.jitter ?? 0.1,
|
|
51
|
+
delay: options?.delay,
|
|
52
|
+
shouldRetry: options?.shouldRetry,
|
|
53
|
+
onRetry: options?.onRetry,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Creates the middleware function.
|
|
58
|
+
* @returns The middleware function
|
|
59
|
+
*/
|
|
60
|
+
middleware() {
|
|
61
|
+
return async (context, next) => {
|
|
62
|
+
const method = context.request.method.toUpperCase();
|
|
63
|
+
// Check if method is eligible for retry
|
|
64
|
+
if (!this.#options.methods.includes(method)) {
|
|
65
|
+
await next();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
let attemptNumber = 0;
|
|
69
|
+
while (true) {
|
|
70
|
+
// Store retry metadata in context for observability
|
|
71
|
+
if (attemptNumber > 0) {
|
|
72
|
+
context.retryAttempt = attemptNumber;
|
|
73
|
+
}
|
|
74
|
+
await next();
|
|
75
|
+
// If no response or we've exhausted retries, stop
|
|
76
|
+
if (!context.response || attemptNumber >= this.#options.limit) {
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
const response = context.response;
|
|
80
|
+
// Check if status code is retryable
|
|
81
|
+
if (!this.#options.statusCodes.includes(response.status)) {
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
// Check custom shouldRetry predicate
|
|
85
|
+
if (this.#options.shouldRetry) {
|
|
86
|
+
const shouldRetry = await this.#options.shouldRetry(response, attemptNumber);
|
|
87
|
+
if (!shouldRetry) {
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Calculate base delay
|
|
92
|
+
let delay = this.#calculateDelay(attemptNumber, response);
|
|
93
|
+
// Check Retry-After header
|
|
94
|
+
const retryAfterDelay = this.#parseRetryAfter(response);
|
|
95
|
+
if (retryAfterDelay !== null) {
|
|
96
|
+
// If Retry-After exceeds maxRetryAfter, don't retry
|
|
97
|
+
if (retryAfterDelay > this.#options.maxRetryAfter) {
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
// Use the larger of computed delay or Retry-After
|
|
101
|
+
delay = Math.max(delay, retryAfterDelay);
|
|
102
|
+
}
|
|
103
|
+
// Invoke onRetry callback
|
|
104
|
+
this.#options.onRetry?.(attemptNumber, response, delay);
|
|
105
|
+
// Wait before retry
|
|
106
|
+
await this.#sleep(delay);
|
|
107
|
+
// Reset response for next attempt
|
|
108
|
+
context.response = null;
|
|
109
|
+
attemptNumber++;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Calculates the delay for a given attempt with exponential backoff and jitter.
|
|
115
|
+
*/
|
|
116
|
+
#calculateDelay(attemptNumber, response) {
|
|
117
|
+
let baseDelay;
|
|
118
|
+
if (this.#options.delay) {
|
|
119
|
+
baseDelay = this.#options.delay(attemptNumber, response);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
// Default exponential backoff: 1s, 2s, 4s, 8s, ...
|
|
123
|
+
baseDelay = Math.min(1000 * Math.pow(2, attemptNumber), this.#options.backoffLimit);
|
|
124
|
+
}
|
|
125
|
+
// Apply jitter
|
|
126
|
+
return this.#applyJitter(baseDelay);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Applies jitter to a delay value.
|
|
130
|
+
*/
|
|
131
|
+
#applyJitter(delay) {
|
|
132
|
+
if (this.#options.jitter <= 0) {
|
|
133
|
+
return delay;
|
|
134
|
+
}
|
|
135
|
+
const jitterRange = delay * this.#options.jitter;
|
|
136
|
+
// Random value between -jitterRange and +jitterRange
|
|
137
|
+
const jitter = (Math.random() * 2 - 1) * jitterRange;
|
|
138
|
+
return Math.max(0, Math.round(delay + jitter));
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Parses the Retry-After header and returns the delay in milliseconds.
|
|
142
|
+
* Supports both delta-seconds and HTTP-date formats.
|
|
143
|
+
*/
|
|
144
|
+
#parseRetryAfter(response) {
|
|
145
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
146
|
+
if (!retryAfter) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
// Try parsing as seconds (integer)
|
|
150
|
+
const seconds = parseInt(retryAfter, 10);
|
|
151
|
+
if (!isNaN(seconds)) {
|
|
152
|
+
return seconds * 1000;
|
|
153
|
+
}
|
|
154
|
+
// Try parsing as HTTP-date
|
|
155
|
+
const date = Date.parse(retryAfter);
|
|
156
|
+
if (!isNaN(date)) {
|
|
157
|
+
return Math.max(0, date - Date.now());
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Sleep for the specified number of milliseconds.
|
|
163
|
+
*/
|
|
164
|
+
#sleep(ms) {
|
|
165
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Creates a retry middleware with the given options.
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* ```typescript
|
|
173
|
+
* const client = new FetchClient();
|
|
174
|
+
* client.use(createRetryMiddleware({ limit: 3 }));
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export function createRetryMiddleware(options) {
|
|
178
|
+
return new RetryMiddleware(options).middleware();
|
|
179
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export class MockHistoryImpl {
|
|
5
5
|
#get = [];
|
|
6
|
+
#head = [];
|
|
6
7
|
#post = [];
|
|
7
8
|
#put = [];
|
|
8
9
|
#patch = [];
|
|
@@ -11,6 +12,9 @@ export class MockHistoryImpl {
|
|
|
11
12
|
get get() {
|
|
12
13
|
return [...this.#get];
|
|
13
14
|
}
|
|
15
|
+
get head() {
|
|
16
|
+
return [...this.#head];
|
|
17
|
+
}
|
|
14
18
|
get post() {
|
|
15
19
|
return [...this.#post];
|
|
16
20
|
}
|
|
@@ -35,6 +39,9 @@ export class MockHistoryImpl {
|
|
|
35
39
|
case "GET":
|
|
36
40
|
this.#get.push(request);
|
|
37
41
|
break;
|
|
42
|
+
case "HEAD":
|
|
43
|
+
this.#head.push(request);
|
|
44
|
+
break;
|
|
38
45
|
case "POST":
|
|
39
46
|
this.#post.push(request);
|
|
40
47
|
break;
|
|
@@ -54,6 +61,7 @@ export class MockHistoryImpl {
|
|
|
54
61
|
*/
|
|
55
62
|
clear() {
|
|
56
63
|
this.#get = [];
|
|
64
|
+
this.#head = [];
|
|
57
65
|
this.#post = [];
|
|
58
66
|
this.#put = [];
|
|
59
67
|
this.#patch = [];
|
|
@@ -40,6 +40,13 @@ export class MockRegistry {
|
|
|
40
40
|
onGet(url) {
|
|
41
41
|
return this.#addMock("GET", url);
|
|
42
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Creates a mock for HEAD requests matching the given URL.
|
|
45
|
+
* @param url - URL string or RegExp to match
|
|
46
|
+
*/
|
|
47
|
+
onHead(url) {
|
|
48
|
+
return this.#addMock("HEAD", url);
|
|
49
|
+
}
|
|
43
50
|
/**
|
|
44
51
|
* Creates a mock for POST requests matching the given URL.
|
|
45
52
|
* @param url - URL string or RegExp to match
|
package/package.json
CHANGED
package/script/mod.js
CHANGED
|
@@ -14,9 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.createPerDomainCircuitBreakerMiddleware = exports.createCircuitBreakerMiddleware = exports.CircuitOpenError = exports.CircuitBreakerMiddleware = exports.circuitBreakerGroupByDomain = exports.CircuitBreaker = exports.FetchClientProvider = exports.defaultProviderInstance = exports.FetchClientCache = exports.ProblemDetails = exports.FetchClient = void 0;
|
|
17
|
+
exports.middleware = exports.RateLimiter = exports.rateLimiterGroupByDomain = exports.RateLimitMiddleware = exports.RateLimitError = exports.createRateLimitMiddleware = exports.createPerDomainRateLimitMiddleware = exports.RetryMiddleware = exports.createRetryMiddleware = exports.createPerDomainCircuitBreakerMiddleware = exports.createCircuitBreakerMiddleware = exports.CircuitOpenError = exports.CircuitBreakerMiddleware = exports.circuitBreakerGroupByDomain = exports.CircuitBreaker = exports.FetchClientProvider = exports.defaultProviderInstance = exports.FetchClientCache = exports.ProblemDetails = exports.ResponsePromise = exports.FetchClientError = exports.FetchClient = void 0;
|
|
18
18
|
var FetchClient_js_1 = require("./src/FetchClient.js");
|
|
19
19
|
Object.defineProperty(exports, "FetchClient", { enumerable: true, get: function () { return FetchClient_js_1.FetchClient; } });
|
|
20
|
+
var FetchClientError_js_1 = require("./src/FetchClientError.js");
|
|
21
|
+
Object.defineProperty(exports, "FetchClientError", { enumerable: true, get: function () { return FetchClientError_js_1.FetchClientError; } });
|
|
22
|
+
var ResponsePromise_js_1 = require("./src/ResponsePromise.js");
|
|
23
|
+
Object.defineProperty(exports, "ResponsePromise", { enumerable: true, get: function () { return ResponsePromise_js_1.ResponsePromise; } });
|
|
20
24
|
var ProblemDetails_js_1 = require("./src/ProblemDetails.js");
|
|
21
25
|
Object.defineProperty(exports, "ProblemDetails", { enumerable: true, get: function () { return ProblemDetails_js_1.ProblemDetails; } });
|
|
22
26
|
var FetchClientCache_js_1 = require("./src/FetchClientCache.js");
|
|
@@ -33,3 +37,92 @@ Object.defineProperty(exports, "CircuitBreakerMiddleware", { enumerable: true, g
|
|
|
33
37
|
Object.defineProperty(exports, "CircuitOpenError", { enumerable: true, get: function () { return CircuitBreakerMiddleware_js_1.CircuitOpenError; } });
|
|
34
38
|
Object.defineProperty(exports, "createCircuitBreakerMiddleware", { enumerable: true, get: function () { return CircuitBreakerMiddleware_js_1.createCircuitBreakerMiddleware; } });
|
|
35
39
|
Object.defineProperty(exports, "createPerDomainCircuitBreakerMiddleware", { enumerable: true, get: function () { return CircuitBreakerMiddleware_js_1.createPerDomainCircuitBreakerMiddleware; } });
|
|
40
|
+
var RetryMiddleware_js_1 = require("./src/RetryMiddleware.js");
|
|
41
|
+
Object.defineProperty(exports, "createRetryMiddleware", { enumerable: true, get: function () { return RetryMiddleware_js_1.createRetryMiddleware; } });
|
|
42
|
+
Object.defineProperty(exports, "RetryMiddleware", { enumerable: true, get: function () { return RetryMiddleware_js_1.RetryMiddleware; } });
|
|
43
|
+
var RateLimitMiddleware_js_1 = require("./src/RateLimitMiddleware.js");
|
|
44
|
+
Object.defineProperty(exports, "createPerDomainRateLimitMiddleware", { enumerable: true, get: function () { return RateLimitMiddleware_js_1.createPerDomainRateLimitMiddleware; } });
|
|
45
|
+
Object.defineProperty(exports, "createRateLimitMiddleware", { enumerable: true, get: function () { return RateLimitMiddleware_js_1.createRateLimitMiddleware; } });
|
|
46
|
+
Object.defineProperty(exports, "RateLimitError", { enumerable: true, get: function () { return RateLimitMiddleware_js_1.RateLimitError; } });
|
|
47
|
+
Object.defineProperty(exports, "RateLimitMiddleware", { enumerable: true, get: function () { return RateLimitMiddleware_js_1.RateLimitMiddleware; } });
|
|
48
|
+
var RateLimiter_js_1 = require("./src/RateLimiter.js");
|
|
49
|
+
Object.defineProperty(exports, "rateLimiterGroupByDomain", { enumerable: true, get: function () { return RateLimiter_js_1.groupByDomain; } });
|
|
50
|
+
Object.defineProperty(exports, "RateLimiter", { enumerable: true, get: function () { return RateLimiter_js_1.RateLimiter; } });
|
|
51
|
+
const RetryMiddleware_js_2 = require("./src/RetryMiddleware.js");
|
|
52
|
+
const RateLimitMiddleware_js_2 = require("./src/RateLimitMiddleware.js");
|
|
53
|
+
const CircuitBreakerMiddleware_js_2 = require("./src/CircuitBreakerMiddleware.js");
|
|
54
|
+
const DefaultHelpers_js_1 = require("./src/DefaultHelpers.js");
|
|
55
|
+
/**
|
|
56
|
+
* Convenience middleware factory functions for use with FetchClient.use()
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* import { FetchClient, middleware } from "@foundatiofx/fetchclient";
|
|
61
|
+
*
|
|
62
|
+
* const client = new FetchClient();
|
|
63
|
+
* client.use(
|
|
64
|
+
* middleware.retry({ limit: 3 }),
|
|
65
|
+
* middleware.rateLimit({ maxRequests: 100, windowSeconds: 60 }),
|
|
66
|
+
* middleware.circuitBreaker({ failureThreshold: 5 })
|
|
67
|
+
* );
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
exports.middleware = {
|
|
71
|
+
/** Retry failed requests with exponential backoff and jitter */
|
|
72
|
+
retry: RetryMiddleware_js_2.createRetryMiddleware,
|
|
73
|
+
/** Rate limit requests to prevent overwhelming servers */
|
|
74
|
+
rateLimit: RateLimitMiddleware_js_2.createRateLimitMiddleware,
|
|
75
|
+
/** Per-domain rate limit (each domain tracked separately) */
|
|
76
|
+
perDomainRateLimit: RateLimitMiddleware_js_2.createPerDomainRateLimitMiddleware,
|
|
77
|
+
/** Circuit breaker for fault tolerance */
|
|
78
|
+
circuitBreaker: CircuitBreakerMiddleware_js_2.createCircuitBreakerMiddleware,
|
|
79
|
+
/** Per-domain circuit breaker (each domain tracked separately) */
|
|
80
|
+
perDomainCircuitBreaker: CircuitBreakerMiddleware_js_2.createPerDomainCircuitBreakerMiddleware,
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Default export for convenient access to all HTTP methods.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```typescript
|
|
87
|
+
* import fc from "@foundatiofx/fetchclient";
|
|
88
|
+
*
|
|
89
|
+
* // Configure middleware
|
|
90
|
+
* fc.use(fc.middleware.retry({ limit: 3 }));
|
|
91
|
+
*
|
|
92
|
+
* // Use JSON methods (recommended)
|
|
93
|
+
* const { data: user } = await fc.getJSON<User>("/api/user/1");
|
|
94
|
+
* const { data: created } = await fc.postJSON<User>("/api/users", { name: "Alice" });
|
|
95
|
+
*
|
|
96
|
+
* // Or use fluent API for other response types
|
|
97
|
+
* const html = await fc.get("/page").text();
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
const fetchClient = {
|
|
101
|
+
/** Sends a GET request. Use `.json<T>()` for typed JSON response. */
|
|
102
|
+
get: (url, options) => (0, DefaultHelpers_js_1.useFetchClient)().get(url, options),
|
|
103
|
+
/** Sends a POST request. Use `.json<T>()` for typed JSON response. */
|
|
104
|
+
post: (url, body, options) => (0, DefaultHelpers_js_1.useFetchClient)().post(url, body, options),
|
|
105
|
+
/** Sends a PUT request. Use `.json<T>()` for typed JSON response. */
|
|
106
|
+
put: (url, body, options) => (0, DefaultHelpers_js_1.useFetchClient)().put(url, body, options),
|
|
107
|
+
/** Sends a PATCH request. Use `.json<T>()` for typed JSON response. */
|
|
108
|
+
patch: (url, body, options) => (0, DefaultHelpers_js_1.useFetchClient)().patch(url, body, options),
|
|
109
|
+
/** Sends a DELETE request. Use `.json<T>()` for typed JSON response. */
|
|
110
|
+
delete: (url, options) => (0, DefaultHelpers_js_1.useFetchClient)().delete(url, options),
|
|
111
|
+
/** Sends a HEAD request. */
|
|
112
|
+
head: (url, options) => (0, DefaultHelpers_js_1.useFetchClient)().head(url, options),
|
|
113
|
+
/** Sends a GET request and returns parsed JSON in response.data */
|
|
114
|
+
getJSON: DefaultHelpers_js_1.getJSON,
|
|
115
|
+
/** Sends a POST request and returns parsed JSON in response.data */
|
|
116
|
+
postJSON: DefaultHelpers_js_1.postJSON,
|
|
117
|
+
/** Sends a PUT request and returns parsed JSON in response.data */
|
|
118
|
+
putJSON: DefaultHelpers_js_1.putJSON,
|
|
119
|
+
/** Sends a PATCH request and returns parsed JSON in response.data */
|
|
120
|
+
patchJSON: DefaultHelpers_js_1.patchJSON,
|
|
121
|
+
/** Sends a DELETE request and returns parsed JSON in response.data */
|
|
122
|
+
deleteJSON: DefaultHelpers_js_1.deleteJSON,
|
|
123
|
+
/** Adds middleware to the default provider */
|
|
124
|
+
use: DefaultHelpers_js_1.useMiddleware,
|
|
125
|
+
/** Middleware factory functions */
|
|
126
|
+
middleware: exports.middleware,
|
|
127
|
+
};
|
|
128
|
+
exports.default = fetchClient;
|
|
@@ -31,7 +31,7 @@ function useFetchClient(options) {
|
|
|
31
31
|
* Sends a GET request to the specified URL using the default client and provider and returns the response as JSON.
|
|
32
32
|
* @param url - The URL to send the GET request to.
|
|
33
33
|
* @param options - Optional request options.
|
|
34
|
-
* @returns A promise that resolves to the response
|
|
34
|
+
* @returns A promise that resolves to the response with parsed JSON in `data`.
|
|
35
35
|
*/
|
|
36
36
|
function getJSON(url, options) {
|
|
37
37
|
return useFetchClient().getJSON(url, options);
|
|
@@ -43,7 +43,7 @@ function getJSON(url, options) {
|
|
|
43
43
|
* @param {string} url - The URL to send the request to.
|
|
44
44
|
* @param {object | string | FormData} [body] - The JSON payload or form data to send with the request.
|
|
45
45
|
* @param {RequestOptions} [options] - Additional options for the request.
|
|
46
|
-
* @returns
|
|
46
|
+
* @returns A promise that resolves to the response with parsed JSON in `data`.
|
|
47
47
|
*/
|
|
48
48
|
function postJSON(url, body, options) {
|
|
49
49
|
return useFetchClient().postJSON(url, body, options);
|
|
@@ -55,7 +55,7 @@ function postJSON(url, body, options) {
|
|
|
55
55
|
* @param {string} url - The URL to send the request to.
|
|
56
56
|
* @param {object | string} [body] - The JSON payload to send with the request.
|
|
57
57
|
* @param {RequestOptions} [options] - Additional options for the request.
|
|
58
|
-
* @returns
|
|
58
|
+
* @returns A promise that resolves to the response with parsed JSON in `data`.
|
|
59
59
|
*/
|
|
60
60
|
function putJSON(url, body, options) {
|
|
61
61
|
return useFetchClient().putJSON(url, body, options);
|
|
@@ -67,7 +67,7 @@ function putJSON(url, body, options) {
|
|
|
67
67
|
* @param {string} url - The URL to send the request to.
|
|
68
68
|
* @param {object | string} [body] - The JSON payload to send with the request.
|
|
69
69
|
* @param {RequestOptions} [options] - Additional options for the request.
|
|
70
|
-
* @returns
|
|
70
|
+
* @returns A promise that resolves to the response with parsed JSON in `data`.
|
|
71
71
|
*/
|
|
72
72
|
function patchJSON(url, body, options) {
|
|
73
73
|
return useFetchClient().patchJSON(url, body, options);
|
|
@@ -78,7 +78,7 @@ function patchJSON(url, body, options) {
|
|
|
78
78
|
* @template T - The type of the response data.
|
|
79
79
|
* @param {string} url - The URL to send the request to.
|
|
80
80
|
* @param {RequestOptions} [options] - Additional options for the request.
|
|
81
|
-
* @returns
|
|
81
|
+
* @returns A promise that resolves to the response with parsed JSON in `data`.
|
|
82
82
|
*/
|
|
83
83
|
function deleteJSON(url, options) {
|
|
84
84
|
return useFetchClient().deleteJSON(url, options);
|
|
@@ -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
|
|
108
|
+
* @returns A ResponsePromise that resolves to the response. Can use `.json<T>()` for typed JSON.
|
|
107
109
|
*/
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
get(url, options) {
|
|
111
|
+
const mergedOptions = {
|
|
110
112
|
...this.options.defaultRequestOptions,
|
|
111
113
|
...options,
|
|
112
114
|
};
|
|
113
|
-
const
|
|
114
|
-
return
|
|
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
|
|
124
|
+
* @returns A promise that resolves to the response with parsed JSON in `data`.
|
|
121
125
|
*/
|
|
122
|
-
getJSON(url, options) {
|
|
123
|
-
|
|
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
|
|
136
|
+
* @returns A ResponsePromise that resolves to the response. Can use `.json<T>()` for typed JSON.
|
|
132
137
|
*/
|
|
133
|
-
|
|
134
|
-
|
|
138
|
+
post(url, body, options) {
|
|
139
|
+
const mergedOptions = {
|
|
135
140
|
...this.options.defaultRequestOptions,
|
|
136
141
|
...options,
|
|
137
142
|
};
|
|
138
|
-
const
|
|
139
|
-
return
|
|
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
|
|
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
|
|
164
|
+
* @returns A ResponsePromise that resolves to the response. Can use `.json<T>()` for typed JSON.
|
|
159
165
|
*/
|
|
160
|
-
|
|
161
|
-
|
|
166
|
+
put(url, body, options) {
|
|
167
|
+
const mergedOptions = {
|
|
162
168
|
...this.options.defaultRequestOptions,
|
|
163
169
|
...options,
|
|
164
170
|
};
|
|
165
|
-
const
|
|
166
|
-
return
|
|
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
|
|
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
|
|
192
|
+
* @returns A ResponsePromise that resolves to the response. Can use `.json<T>()` for typed JSON.
|
|
186
193
|
*/
|
|
187
|
-
|
|
188
|
-
|
|
194
|
+
patch(url, body, options) {
|
|
195
|
+
const mergedOptions = {
|
|
189
196
|
...this.options.defaultRequestOptions,
|
|
190
197
|
...options,
|
|
191
198
|
};
|
|
192
|
-
const
|
|
193
|
-
return
|
|
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
|
|
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
|
|
220
|
+
* @returns A ResponsePromise that resolves to the response. Can use `.json<T>()` for typed JSON.
|
|
213
221
|
*/
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
delete(url, options) {
|
|
223
|
+
const mergedOptions = {
|
|
216
224
|
...this.options.defaultRequestOptions,
|
|
217
225
|
...options,
|
|
218
226
|
};
|
|
219
|
-
const
|
|
220
|
-
return
|
|
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
|
|
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;
|