|
| 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