@foundatiofx/fetchclient 1.0.0 → 1.1.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 +3 -1
- package/esm/src/CircuitBreaker.js +356 -0
- package/esm/src/CircuitBreakerMiddleware.js +167 -0
- package/esm/src/DefaultHelpers.js +16 -0
- package/esm/src/FetchClient.js +8 -6
- package/esm/src/FetchClientCache.js +85 -8
- package/esm/src/FetchClientProvider.js +58 -3
- package/esm/src/mocks/MockHistory.js +63 -0
- package/esm/src/mocks/MockRegistry.js +267 -0
- package/esm/src/mocks/MockResponseBuilder.js +88 -0
- package/esm/src/mocks/mod.js +24 -0
- package/esm/src/mocks/types.js +1 -0
- package/package.json +12 -2
- package/readme.md +88 -233
- package/script/mod.js +9 -1
- package/script/src/CircuitBreaker.js +361 -0
- package/script/src/CircuitBreakerMiddleware.js +174 -0
- package/script/src/DefaultHelpers.js +18 -0
- package/script/src/FetchClient.js +8 -6
- package/script/src/FetchClientCache.js +85 -8
- package/script/src/FetchClientProvider.js +58 -3
- package/script/src/mocks/MockHistory.js +67 -0
- package/script/src/mocks/MockRegistry.js +271 -0
- package/script/src/mocks/MockResponseBuilder.js +92 -0
- package/script/src/mocks/mod.js +29 -0
- package/script/src/mocks/types.js +2 -0
- package/types/deps/jsr.io/@std/assert/1.0.18/almost_equals.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.18/array_includes.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.18/assert.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/assertion_error.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/1.0.18/equal.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/equals.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/exists.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/fail.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/false.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/greater.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/greater_or_equal.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/instance_of.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/is_error.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/less.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/less_or_equal.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/match.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/mod.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/not_equals.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/not_instance_of.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/not_match.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/not_strict_equals.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/object_match.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/rejects.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/strict_equals.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/string_includes.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/throws.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/unimplemented.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/assert/{1.0.14 → 1.0.18}/unreachable.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/internal/1.0.12/build_message.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/internal/{1.0.10 → 1.0.12}/diff.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/internal/1.0.12/diff_str.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/internal/{1.0.10 → 1.0.12}/format.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/internal/{1.0.10 → 1.0.12}/styles.d.ts.map +1 -1
- package/types/deps/jsr.io/@std/internal/1.0.12/types.d.ts.map +1 -0
- package/types/mod.d.ts +3 -1
- package/types/mod.d.ts.map +1 -1
- package/types/src/CircuitBreaker.d.ts +154 -0
- package/types/src/CircuitBreaker.d.ts.map +1 -0
- package/types/src/CircuitBreakerMiddleware.d.ts +93 -0
- package/types/src/CircuitBreakerMiddleware.d.ts.map +1 -0
- package/types/src/DefaultHelpers.d.ts +13 -0
- package/types/src/DefaultHelpers.d.ts.map +1 -1
- package/types/src/FetchClient.d.ts.map +1 -1
- package/types/src/FetchClientCache.d.ts +26 -1
- package/types/src/FetchClientCache.d.ts.map +1 -1
- package/types/src/FetchClientProvider.d.ts +24 -0
- package/types/src/FetchClientProvider.d.ts.map +1 -1
- package/types/src/RequestOptions.d.ts +6 -1
- package/types/src/RequestOptions.d.ts.map +1 -1
- package/types/src/mocks/MockHistory.d.ts +22 -0
- package/types/src/mocks/MockHistory.d.ts.map +1 -0
- package/types/src/mocks/MockRegistry.d.ts +113 -0
- package/types/src/mocks/MockRegistry.d.ts.map +1 -0
- package/types/src/mocks/MockResponseBuilder.d.ts +60 -0
- package/types/src/mocks/MockResponseBuilder.d.ts.map +1 -0
- package/types/src/mocks/mod.d.ts +26 -0
- package/types/src/mocks/mod.d.ts.map +1 -0
- package/types/src/mocks/types.d.ts +47 -0
- package/types/src/mocks/types.d.ts.map +1 -0
- package/types/src/tests/Caching.test.d.ts.map +1 -0
- package/types/src/tests/CircuitBreaker.test.d.ts.map +1 -0
- package/types/src/tests/ErrorHandling.test.d.ts.map +1 -0
- package/types/src/tests/HttpMethods.test.d.ts.map +1 -0
- package/types/src/tests/Integration.test.d.ts.map +1 -0
- package/types/src/tests/JsonParsing.test.d.ts.map +1 -0
- package/types/src/tests/Middleware.test.d.ts.map +1 -0
- package/types/src/tests/MockRegistry.test.d.ts.map +1 -0
- package/types/src/tests/Provider.test.d.ts.map +1 -0
- package/types/src/tests/RateLimit.test.d.ts.map +1 -0
- package/types/src/tests/TimeoutAbort.test.d.ts.map +1 -0
- package/types/src/tests/UrlBuilding.test.d.ts.map +1 -0
- package/types/deps/jsr.io/@std/assert/1.0.14/almost_equals.d.ts.map +0 -1
- package/types/deps/jsr.io/@std/assert/1.0.14/array_includes.d.ts.map +0 -1
- package/types/deps/jsr.io/@std/assert/1.0.14/assert.d.ts.map +0 -1
- package/types/deps/jsr.io/@std/assert/1.0.14/equal.d.ts.map +0 -1
- package/types/deps/jsr.io/@std/internal/1.0.10/build_message.d.ts.map +0 -1
- package/types/deps/jsr.io/@std/internal/1.0.10/diff_str.d.ts.map +0 -1
- package/types/deps/jsr.io/@std/internal/1.0.10/types.d.ts.map +0 -1
- package/types/src/FetchClient.test.d.ts.map +0 -1
- package/types/src/RateLimit.test.d.ts.map +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foundatiofx/fetchclient",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A typed JSON fetch client with middleware support for Deno, Node and the browser.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"Fetch",
|
|
@@ -35,6 +35,16 @@
|
|
|
35
35
|
"types": "./types/mod.d.ts",
|
|
36
36
|
"default": "./script/mod.js"
|
|
37
37
|
}
|
|
38
|
+
},
|
|
39
|
+
"./mocks": {
|
|
40
|
+
"import": {
|
|
41
|
+
"types": "./types/src/mocks/mod.d.ts",
|
|
42
|
+
"default": "./esm/src/mocks/mod.js"
|
|
43
|
+
},
|
|
44
|
+
"require": {
|
|
45
|
+
"types": "./types/src/mocks/mod.d.ts",
|
|
46
|
+
"default": "./script/src/mocks/mod.js"
|
|
47
|
+
}
|
|
38
48
|
}
|
|
39
49
|
},
|
|
40
50
|
"scripts": {
|
|
@@ -43,7 +53,7 @@
|
|
|
43
53
|
"devDependencies": {
|
|
44
54
|
"@types/node": "^20.9.0",
|
|
45
55
|
"picocolors": "^1.0.0",
|
|
46
|
-
"zod": "^4.
|
|
56
|
+
"zod": "^4.3.6",
|
|
47
57
|
"@deno/shim-deno": "~0.18.0"
|
|
48
58
|
},
|
|
49
59
|
"_generatedBy": "dnt@dev"
|
package/readme.md
CHANGED
|
@@ -6,298 +6,153 @@
|
|
|
6
6
|
[](https://github.com/foundatiofx/foundatio/actions)
|
|
7
7
|
[](https://discord.gg/6HxgFCx)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
-
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
support
|
|
26
|
-
- [Error Handling](#error-handling) - Comprehensive error handling with Problem
|
|
27
|
-
Details
|
|
28
|
-
- [Authentication](#authentication) - Built-in Bearer token support
|
|
29
|
-
- [Base URL](#base-url) - Global base URL configuration
|
|
30
|
-
- [Loading State](#loading-state) - Track request loading state with events
|
|
9
|
+
FetchClient is a tiny, typed wrapper around `fetch` with JSON helpers, caching,
|
|
10
|
+
middleware, rate limiting, circuit breaker, timeouts, and friendly error
|
|
11
|
+
handling.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Typed JSON helpers** - `getJSON`, `postJSON`, `putJSON`, `patchJSON`,
|
|
16
|
+
`deleteJSON`
|
|
17
|
+
- **Two API styles** - Functional (no classes) or class-based - your choice
|
|
18
|
+
- **Response caching** - TTL-based caching with tags for grouped invalidation
|
|
19
|
+
- **Middleware** - Intercept requests/responses for logging, auth, transforms
|
|
20
|
+
- **Rate limiting** - Per-domain rate limits with automatic header detection
|
|
21
|
+
- **Circuit breaker** - Prevent cascading failures when services go down
|
|
22
|
+
- **Timeouts** - Request timeouts with AbortSignal support
|
|
23
|
+
- **Error handling** - RFC 7807 Problem Details support
|
|
24
|
+
- **Testing** - MockRegistry for mocking HTTP in tests
|
|
31
25
|
|
|
32
26
|
## Install
|
|
33
27
|
|
|
34
|
-
```
|
|
35
|
-
npm install
|
|
28
|
+
```bash
|
|
29
|
+
npm install @foundatiofx/fetchclient
|
|
36
30
|
```
|
|
37
31
|
|
|
38
|
-
##
|
|
32
|
+
## Quick Example
|
|
39
33
|
|
|
40
|
-
|
|
34
|
+
FetchClient works two ways - pick whichever style you prefer:
|
|
41
35
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
### Typed Response
|
|
36
|
+
### Functional API (no classes)
|
|
45
37
|
|
|
46
38
|
```ts
|
|
47
|
-
import {
|
|
39
|
+
import { getJSON, postJSON, setBaseUrl } from "@foundatiofx/fetchclient";
|
|
48
40
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
const client = new FetchClient();
|
|
54
|
-
const response = await client.getJSON<Products>(
|
|
55
|
-
`https://dummyjson.com/products/search?q=iphone&limit=10`,
|
|
56
|
-
);
|
|
41
|
+
// Optional: configure once at startup
|
|
42
|
+
setBaseUrl("https://api.example.com");
|
|
57
43
|
|
|
58
|
-
|
|
44
|
+
// Use simple functions anywhere
|
|
45
|
+
const { data: users } = await getJSON<User[]>("/users");
|
|
46
|
+
const { data: created } = await postJSON<User>("/users", { name: "Alice" });
|
|
59
47
|
```
|
|
60
48
|
|
|
61
|
-
|
|
49
|
+
Or use `getFetchClient()` to avoid multiple imports:
|
|
62
50
|
|
|
63
51
|
```ts
|
|
64
|
-
import {
|
|
52
|
+
import { getFetchClient, setBaseUrl } from "@foundatiofx/fetchclient";
|
|
65
53
|
|
|
66
|
-
|
|
67
|
-
type Products = { products: Product[] };
|
|
68
|
-
|
|
69
|
-
const response = await postJSON<Product>(
|
|
70
|
-
"https://dummyjson.com/products/add",
|
|
71
|
-
{
|
|
72
|
-
name: "iPhone 13",
|
|
73
|
-
},
|
|
74
|
-
);
|
|
54
|
+
setBaseUrl("https://api.example.com");
|
|
75
55
|
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
56
|
+
const client = getFetchClient();
|
|
57
|
+
const { data: users } = await client.getJSON<User[]>("/users");
|
|
58
|
+
const { data: created } = await client.postJSON<User>("/users", {
|
|
59
|
+
name: "Alice",
|
|
60
|
+
});
|
|
79
61
|
```
|
|
80
62
|
|
|
81
|
-
###
|
|
63
|
+
### Class-Based API
|
|
82
64
|
|
|
83
65
|
```ts
|
|
84
|
-
import { FetchClient
|
|
85
|
-
|
|
86
|
-
setModelValidator(async (data: object | null) => {
|
|
87
|
-
// use zod or any other validator
|
|
88
|
-
const problem = new ProblemDetails();
|
|
89
|
-
const d = data as { password: string };
|
|
90
|
-
if (d?.password?.length < 6) {
|
|
91
|
-
problem.errors.password = [
|
|
92
|
-
"Password must be longer than or equal to 6 characters.",
|
|
93
|
-
];
|
|
94
|
-
}
|
|
95
|
-
return problem;
|
|
96
|
-
});
|
|
66
|
+
import { FetchClient } from "@foundatiofx/fetchclient";
|
|
97
67
|
|
|
98
|
-
const client = new FetchClient();
|
|
99
|
-
const data =
|
|
100
|
-
email: "test@test",
|
|
101
|
-
password: "test",
|
|
102
|
-
};
|
|
103
|
-
|
|
104
|
-
const response = await client.postJSON(
|
|
105
|
-
"https://jsonplaceholder.typicode.com/todos/1",
|
|
106
|
-
data,
|
|
107
|
-
);
|
|
108
|
-
|
|
109
|
-
if (!response.ok) {
|
|
110
|
-
// check response problem
|
|
111
|
-
console.log(response.problem.detail);
|
|
112
|
-
}
|
|
68
|
+
const client = new FetchClient({ baseUrl: "https://api.example.com" });
|
|
69
|
+
const { data } = await client.getJSON<User[]>("/users");
|
|
113
70
|
```
|
|
114
71
|
|
|
115
|
-
|
|
72
|
+
All styles share the same configuration - the functional API wraps a
|
|
73
|
+
[default provider](https://fetchclient.foundatio.dev/guide/provider#default-provider).
|
|
116
74
|
|
|
117
|
-
|
|
118
|
-
import { FetchClient } from "@foundatiofx/fetchclient";
|
|
75
|
+
## Caching
|
|
119
76
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
cacheKey: ["todos", "1"],
|
|
127
|
-
cacheDuration: 1000 * 60, // expires in 1 minute
|
|
128
|
-
},
|
|
129
|
-
);
|
|
77
|
+
```ts
|
|
78
|
+
const response = await client.getJSON<User>("/api/users/1", {
|
|
79
|
+
cacheKey: ["users", "1"],
|
|
80
|
+
cacheDuration: 60000, // 1 minute
|
|
81
|
+
cacheTags: ["users"],
|
|
82
|
+
});
|
|
130
83
|
|
|
131
|
-
//
|
|
132
|
-
client.cache.
|
|
84
|
+
// Invalidate by tag
|
|
85
|
+
client.cache.deleteByTag("users");
|
|
133
86
|
```
|
|
134
87
|
|
|
135
|
-
|
|
88
|
+
## Middleware
|
|
136
89
|
|
|
137
90
|
```ts
|
|
138
|
-
import {
|
|
139
|
-
|
|
140
|
-
type Products = {
|
|
141
|
-
products: Array<{ id: number; name: string }>;
|
|
142
|
-
};
|
|
91
|
+
import { useMiddleware } from "@foundatiofx/fetchclient";
|
|
143
92
|
|
|
144
93
|
useMiddleware(async (ctx, next) => {
|
|
145
|
-
console.log("
|
|
94
|
+
console.log("Request:", ctx.request.url);
|
|
146
95
|
await next();
|
|
147
|
-
console.log("
|
|
96
|
+
console.log("Response:", ctx.response?.status);
|
|
148
97
|
});
|
|
149
|
-
|
|
150
|
-
const client = new FetchClient();
|
|
151
|
-
const response = await client.getJSON<Products>(
|
|
152
|
-
`https://dummyjson.com/products/search?q=iphone&limit=10`,
|
|
153
|
-
);
|
|
154
98
|
```
|
|
155
99
|
|
|
156
|
-
|
|
100
|
+
## Rate Limiting
|
|
157
101
|
|
|
158
102
|
```ts
|
|
159
|
-
import {
|
|
103
|
+
import { usePerDomainRateLimit } from "@foundatiofx/fetchclient";
|
|
160
104
|
|
|
161
|
-
|
|
162
|
-
useRateLimit({
|
|
105
|
+
usePerDomainRateLimit({
|
|
163
106
|
maxRequests: 100,
|
|
164
107
|
windowSeconds: 60,
|
|
108
|
+
updateFromHeaders: true, // Respect API rate limit headers
|
|
165
109
|
});
|
|
166
|
-
|
|
167
|
-
const client = new FetchClient();
|
|
168
|
-
const response = await client.getJSON(
|
|
169
|
-
`https://api.example.com/data`,
|
|
170
|
-
);
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Request Timeout
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
import { FetchClient } from "@foundatiofx/fetchclient";
|
|
177
|
-
|
|
178
|
-
const client = new FetchClient();
|
|
179
|
-
|
|
180
|
-
// Set timeout for individual requests
|
|
181
|
-
const response = await client.getJSON(
|
|
182
|
-
`https://api.example.com/data`,
|
|
183
|
-
{ timeout: 5000 }, // 5 seconds
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
// Use AbortSignal for cancellation
|
|
187
|
-
const controller = new AbortController();
|
|
188
|
-
setTimeout(() => controller.abort(), 1000);
|
|
189
|
-
|
|
190
|
-
const response2 = await client.getJSON(
|
|
191
|
-
`https://api.example.com/data`,
|
|
192
|
-
{ signal: controller.signal },
|
|
193
|
-
);
|
|
194
110
|
```
|
|
195
111
|
|
|
196
|
-
|
|
112
|
+
## Circuit Breaker
|
|
197
113
|
|
|
198
114
|
```ts
|
|
199
|
-
import {
|
|
115
|
+
import { FetchClientProvider } from "@foundatiofx/fetchclient";
|
|
200
116
|
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
} catch (error) {
|
|
206
|
-
// Handle HTTP errors (4xx, 5xx)
|
|
207
|
-
if (error.problem) {
|
|
208
|
-
console.log(error.problem.title);
|
|
209
|
-
console.log(error.problem.detail);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Or handle specific status codes
|
|
214
|
-
const response = await client.getJSON(
|
|
215
|
-
`https://api.example.com/data`,
|
|
216
|
-
{
|
|
217
|
-
expectedStatusCodes: [404, 500],
|
|
218
|
-
errorCallback: (response) => {
|
|
219
|
-
if (response.status === 404) {
|
|
220
|
-
console.log("Resource not found");
|
|
221
|
-
return true; // Don't throw
|
|
222
|
-
}
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
);
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
### Authentication
|
|
229
|
-
|
|
230
|
-
```ts
|
|
231
|
-
import { FetchClient, setAccessTokenFunc } from "@foundatiofx/fetchclient";
|
|
232
|
-
|
|
233
|
-
// Set global access token function
|
|
234
|
-
setAccessTokenFunc(() => localStorage.getItem("token"));
|
|
235
|
-
|
|
236
|
-
const client = new FetchClient();
|
|
237
|
-
const response = await client.getJSON(`https://api.example.com/data`);
|
|
238
|
-
// Automatically adds Authorization: Bearer <token> header
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
### Base URL
|
|
242
|
-
|
|
243
|
-
```ts
|
|
244
|
-
import { FetchClient, setBaseUrl } from "@foundatiofx/fetchclient";
|
|
245
|
-
|
|
246
|
-
// Set global base URL
|
|
247
|
-
setBaseUrl("https://api.example.com");
|
|
248
|
-
|
|
249
|
-
const client = new FetchClient();
|
|
250
|
-
const response = await client.getJSON(`/users/123`);
|
|
251
|
-
// Requests to https://api.example.com/users/123
|
|
252
|
-
```
|
|
253
|
-
|
|
254
|
-
### Loading State
|
|
255
|
-
|
|
256
|
-
```ts
|
|
257
|
-
import { FetchClient } from "@foundatiofx/fetchclient";
|
|
258
|
-
|
|
259
|
-
const client = new FetchClient();
|
|
260
|
-
|
|
261
|
-
// Track loading state
|
|
262
|
-
client.loading.on((isLoading) => {
|
|
263
|
-
console.log(`Loading: ${isLoading}`);
|
|
117
|
+
const provider = new FetchClientProvider();
|
|
118
|
+
provider.useCircuitBreaker({
|
|
119
|
+
failureThreshold: 5, // Open after 5 failures
|
|
120
|
+
openDurationMs: 30000, // Stay open for 30 seconds
|
|
264
121
|
});
|
|
265
122
|
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
console.log(client.requestCount);
|
|
123
|
+
// When API fails repeatedly, circuit opens
|
|
124
|
+
// Requests return 503 immediately without hitting the API
|
|
269
125
|
```
|
|
270
126
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
[FetchClient Tests](src/FetchClient.test.ts)
|
|
274
|
-
|
|
275
|
-
## Contributing
|
|
127
|
+
## Testing
|
|
276
128
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
deno run test
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
Lint code:
|
|
129
|
+
```ts
|
|
130
|
+
import { FetchClientProvider } from "@foundatiofx/fetchclient";
|
|
131
|
+
import { MockRegistry } from "@foundatiofx/fetchclient/mocks";
|
|
284
132
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
```
|
|
133
|
+
const mocks = new MockRegistry();
|
|
134
|
+
mocks.onGet("/api/users").reply(200, [{ id: 1, name: "Alice" }]);
|
|
288
135
|
|
|
289
|
-
|
|
136
|
+
const provider = new FetchClientProvider();
|
|
137
|
+
mocks.install(provider);
|
|
290
138
|
|
|
291
|
-
|
|
292
|
-
|
|
139
|
+
const client = provider.getFetchClient();
|
|
140
|
+
const { data } = await client.getJSON("/api/users");
|
|
141
|
+
// data = [{ id: 1, name: "Alice" }]
|
|
293
142
|
```
|
|
294
143
|
|
|
295
|
-
|
|
144
|
+
## Documentation
|
|
296
145
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
146
|
+
- Guide & Examples: <https://fetchclient.foundatio.dev>
|
|
147
|
+
- [Getting Started](https://fetchclient.foundatio.dev/guide/getting-started)
|
|
148
|
+
- [Caching](https://fetchclient.foundatio.dev/guide/caching)
|
|
149
|
+
- [Middleware](https://fetchclient.foundatio.dev/guide/middleware)
|
|
150
|
+
- [Rate Limiting](https://fetchclient.foundatio.dev/guide/rate-limiting)
|
|
151
|
+
- [Circuit Breaker](https://fetchclient.foundatio.dev/guide/circuit-breaker)
|
|
152
|
+
- [Error Handling](https://fetchclient.foundatio.dev/guide/error-handling)
|
|
153
|
+
- [Testing](https://fetchclient.foundatio.dev/guide/testing)
|
|
154
|
+
- API Reference: <https://jsr.io/@foundatiofx/fetchclient/doc>
|
|
300
155
|
|
|
301
|
-
|
|
156
|
+
---
|
|
302
157
|
|
|
303
158
|
MIT © [Foundatio](https://exceptionless.com)
|
package/script/mod.js
CHANGED
|
@@ -14,7 +14,7 @@ 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.FetchClientProvider = exports.defaultProviderInstance = exports.FetchClientCache = exports.ProblemDetails = exports.FetchClient = void 0;
|
|
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;
|
|
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
20
|
var ProblemDetails_js_1 = require("./src/ProblemDetails.js");
|
|
@@ -25,3 +25,11 @@ var FetchClientProvider_js_1 = require("./src/FetchClientProvider.js");
|
|
|
25
25
|
Object.defineProperty(exports, "defaultProviderInstance", { enumerable: true, get: function () { return FetchClientProvider_js_1.defaultInstance; } });
|
|
26
26
|
Object.defineProperty(exports, "FetchClientProvider", { enumerable: true, get: function () { return FetchClientProvider_js_1.FetchClientProvider; } });
|
|
27
27
|
__exportStar(require("./src/DefaultHelpers.js"), exports);
|
|
28
|
+
var CircuitBreaker_js_1 = require("./src/CircuitBreaker.js");
|
|
29
|
+
Object.defineProperty(exports, "CircuitBreaker", { enumerable: true, get: function () { return CircuitBreaker_js_1.CircuitBreaker; } });
|
|
30
|
+
Object.defineProperty(exports, "circuitBreakerGroupByDomain", { enumerable: true, get: function () { return CircuitBreaker_js_1.groupByDomain; } });
|
|
31
|
+
var CircuitBreakerMiddleware_js_1 = require("./src/CircuitBreakerMiddleware.js");
|
|
32
|
+
Object.defineProperty(exports, "CircuitBreakerMiddleware", { enumerable: true, get: function () { return CircuitBreakerMiddleware_js_1.CircuitBreakerMiddleware; } });
|
|
33
|
+
Object.defineProperty(exports, "CircuitOpenError", { enumerable: true, get: function () { return CircuitBreakerMiddleware_js_1.CircuitOpenError; } });
|
|
34
|
+
Object.defineProperty(exports, "createCircuitBreakerMiddleware", { enumerable: true, get: function () { return CircuitBreakerMiddleware_js_1.createCircuitBreakerMiddleware; } });
|
|
35
|
+
Object.defineProperty(exports, "createPerDomainCircuitBreakerMiddleware", { enumerable: true, get: function () { return CircuitBreakerMiddleware_js_1.createPerDomainCircuitBreakerMiddleware; } });
|