Skip to content

Commit 6a3a358

Browse files
committed
Add circuit breaker and update docs
1 parent 4bfe229 commit 6a3a358

24 files changed

+4919
-78
lines changed

.claude/settings.local.json

Lines changed: 0 additions & 16 deletions
This file was deleted.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
/npm
33
*.lcov
44
/cov
5+
6+
nul

docs/.vitepress/config.ts

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,28 @@ export default withMermaid(defineConfig({
5353
link: "/guide/what-is-fetchclient",
5454
},
5555
{ text: "Getting Started", link: "/guide/getting-started" },
56-
{ text: "Usage Examples", link: "/guide/usage-examples" },
56+
{ text: "Provider", link: "/guide/provider" },
57+
],
58+
},
59+
{
60+
text: "Features",
61+
items: [
62+
{ text: "Caching", link: "/guide/caching" },
63+
{ text: "Middleware", link: "/guide/middleware" },
64+
{ text: "Rate Limiting", link: "/guide/rate-limiting" },
65+
{ text: "Circuit Breaker", link: "/guide/circuit-breaker" },
66+
{ text: "Error Handling", link: "/guide/error-handling" },
67+
],
68+
},
69+
{
70+
text: "Testing",
71+
items: [
72+
{ text: "MockRegistry", link: "/guide/testing" },
73+
],
74+
},
75+
{
76+
text: "More",
77+
items: [
5778
{ text: "Contributing", link: "/guide/contributing" },
5879
],
5980
},

docs/guide/caching.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
# Caching
2+
3+
FetchClient provides built-in response caching with TTL (time-to-live), cache tags for grouped invalidation, and programmatic cache control.
4+
5+
## Basic Caching
6+
7+
Add `cacheKey` and `cacheDuration` to cache responses:
8+
9+
```ts
10+
import { FetchClient } from "@foundatiofx/fetchclient";
11+
12+
type Todo = { userId: number; id: number; title: string; completed: boolean };
13+
14+
const client = new FetchClient();
15+
const response = await client.getJSON<Todo>(
16+
"https://jsonplaceholder.typicode.com/todos/1",
17+
{
18+
cacheKey: ["todos", "1"],
19+
cacheDuration: 1000 * 60, // 1 minute
20+
},
21+
);
22+
23+
// Subsequent calls with the same cacheKey return cached data
24+
const cached = await client.getJSON<Todo>(
25+
"https://jsonplaceholder.typicode.com/todos/1",
26+
{
27+
cacheKey: ["todos", "1"],
28+
cacheDuration: 1000 * 60,
29+
},
30+
);
31+
// No network request made - data comes from cache
32+
```
33+
34+
## Cache Keys
35+
36+
Cache keys are arrays that get joined with colons. This makes it easy to organize and invalidate related entries:
37+
38+
```ts
39+
// These cache keys become:
40+
// "users:123"
41+
// "users:123:posts"
42+
// "posts:456"
43+
44+
await client.getJSON("/api/users/123", {
45+
cacheKey: ["users", "123"],
46+
});
47+
48+
await client.getJSON("/api/users/123/posts", {
49+
cacheKey: ["users", "123", "posts"],
50+
});
51+
52+
await client.getJSON("/api/posts/456", {
53+
cacheKey: ["posts", "456"],
54+
});
55+
```
56+
57+
## Invalidating Cache
58+
59+
### Delete Specific Entry
60+
61+
```ts
62+
client.cache.delete(["todos", "1"]);
63+
```
64+
65+
### Delete by Prefix
66+
67+
Remove all entries that start with a prefix:
68+
69+
```ts
70+
// Remove all user-related cache entries
71+
client.cache.deleteAll(["users"]);
72+
73+
// This deletes:
74+
// - "users:123"
75+
// - "users:123:posts"
76+
// - "users:456"
77+
// etc.
78+
```
79+
80+
### Clear All Cache
81+
82+
```ts
83+
client.cache.clear();
84+
```
85+
86+
## Cache Tagging
87+
88+
Tags let you group unrelated cache entries and invalidate them together. This is useful when data relationships span different cache keys.
89+
90+
### Adding Tags
91+
92+
```ts
93+
// Cache user data with tags
94+
await client.getJSON("/api/users/1", {
95+
cacheKey: ["users", "1"],
96+
cacheTags: ["users", "active-session"],
97+
});
98+
99+
await client.getJSON("/api/users/2", {
100+
cacheKey: ["users", "2"],
101+
cacheTags: ["users", "active-session"],
102+
});
103+
104+
// Cache posts with overlapping tags
105+
await client.getJSON("/api/posts/1", {
106+
cacheKey: ["posts", "1"],
107+
cacheTags: ["posts", "active-session"],
108+
});
109+
```
110+
111+
### Invalidate by Tag
112+
113+
```ts
114+
// Invalidate all user cache entries
115+
client.cache.deleteByTag("users");
116+
// Removes users/1 and users/2, keeps posts/1
117+
118+
// Invalidate everything related to the session
119+
client.cache.deleteByTag("active-session");
120+
// Removes users/1, users/2, and posts/1
121+
```
122+
123+
### Inspecting Tags
124+
125+
```ts
126+
// Get all tags in use
127+
const tags = client.cache.getTags();
128+
// ["users", "posts", "active-session"]
129+
130+
// Get tags for a specific entry
131+
const entryTags = client.cache.getEntryTags(["posts", "1"]);
132+
// ["posts", "active-session"]
133+
```
134+
135+
## Cache Behavior
136+
137+
- **Automatic cleanup**: Expired entries are removed automatically when accessed
138+
- **Tag cleanup**: Tags are automatically cleaned up when their entries expire or are deleted
139+
- **Memory-based**: Cache is stored in memory and clears on page refresh
140+
- **Per-provider**: Each `FetchClientProvider` has its own cache instance
141+
142+
## Shared Cache with Provider
143+
144+
When using `FetchClientProvider`, all clients share the same cache:
145+
146+
```ts
147+
import { FetchClientProvider } from "@foundatiofx/fetchclient";
148+
149+
const provider = new FetchClientProvider();
150+
151+
const client1 = provider.getFetchClient();
152+
const client2 = provider.getFetchClient();
153+
154+
// Cache entry created by client1
155+
await client1.getJSON("/api/data", {
156+
cacheKey: ["data"],
157+
cacheDuration: 60000,
158+
});
159+
160+
// client2 gets the cached response
161+
await client2.getJSON("/api/data", {
162+
cacheKey: ["data"],
163+
cacheDuration: 60000,
164+
});
165+
166+
// Both clients share the same cache
167+
console.log(client1.cache === client2.cache); // true
168+
```
169+
170+
## Practical Example: User Dashboard
171+
172+
```ts
173+
const client = new FetchClient();
174+
175+
// Load dashboard data with related tags
176+
async function loadDashboard(userId: string) {
177+
const [user, posts, notifications] = await Promise.all([
178+
client.getJSON(`/api/users/${userId}`, {
179+
cacheKey: ["users", userId],
180+
cacheTags: ["dashboard", `user-${userId}`],
181+
cacheDuration: 5 * 60 * 1000, // 5 minutes
182+
}),
183+
client.getJSON(`/api/users/${userId}/posts`, {
184+
cacheKey: ["users", userId, "posts"],
185+
cacheTags: ["dashboard", `user-${userId}`, "posts"],
186+
cacheDuration: 2 * 60 * 1000, // 2 minutes
187+
}),
188+
client.getJSON(`/api/users/${userId}/notifications`, {
189+
cacheKey: ["users", userId, "notifications"],
190+
cacheTags: ["dashboard", `user-${userId}`],
191+
cacheDuration: 30 * 1000, // 30 seconds
192+
}),
193+
]);
194+
195+
return { user, posts, notifications };
196+
}
197+
198+
// Refresh just the posts
199+
function refreshPosts() {
200+
client.cache.deleteByTag("posts");
201+
}
202+
203+
// Refresh entire dashboard
204+
function refreshDashboard() {
205+
client.cache.deleteByTag("dashboard");
206+
}
207+
208+
// User logged out - clear their data
209+
function logout(userId: string) {
210+
client.cache.deleteByTag(`user-${userId}`);
211+
}
212+
```

0 commit comments

Comments
 (0)