@gorgonjs/gorgon 1.6.0 → 1.6.1
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/README.md +1 -1
- package/package.json +10 -3
- package/skills/core/SKILL.md +336 -0
- package/skills/react/SKILL.md +242 -0
- package/.eslintrc +0 -198
- package/CHANGELOG.md +0 -61
- package/__tests__/basic.test.ts +0 -180
- package/__tests__/clearmany.test.ts +0 -97
- package/__tests__/concurrency.test.ts +0 -50
- package/__tests__/datePolicy.test.ts +0 -20
- package/__tests__/debug.test.ts +0 -50
- package/__tests__/hooks.test.ts +0 -181
- package/coverage/base.css +0 -224
- package/coverage/block-navigation.js +0 -87
- package/coverage/clover.xml +0 -433
- package/coverage/coverage-final.json +0 -3
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +0 -131
- package/coverage/library/index.html +0 -116
- package/coverage/library/index.ts.html +0 -1036
- package/coverage/library/provider/index.html +0 -116
- package/coverage/library/provider/memory.ts.html +0 -379
- package/coverage/prettify.css +0 -1
- package/coverage/prettify.js +0 -2
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +0 -196
- package/index.ts +0 -317
- package/provider/memory.ts +0 -98
- package/tsconfig.json +0 -11
- package/vite.config.js +0 -19
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Gorgon
|
|
2
2
|

|
|
3
3
|

|
|
4
|
-

|
|
5
5
|

|
|
6
6
|
|
|
7
7
|
A typescript async based caching library for node or the browser.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gorgonjs/gorgon",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.1",
|
|
4
4
|
"description": "A simple caching library for async functions",
|
|
5
5
|
"homepage": "https://gorgonjs.dev",
|
|
6
6
|
"main": "./dist/index.umd.js",
|
|
@@ -29,11 +29,17 @@
|
|
|
29
29
|
"type": "git",
|
|
30
30
|
"url": "git@github.com:mikevalstar/gorgon.git"
|
|
31
31
|
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"skills",
|
|
35
|
+
"!skills/_artifacts"
|
|
36
|
+
],
|
|
32
37
|
"keywords": [
|
|
33
38
|
"cache",
|
|
34
39
|
"promise",
|
|
35
40
|
"async",
|
|
36
|
-
"typescript"
|
|
41
|
+
"typescript",
|
|
42
|
+
"tanstack-intent"
|
|
37
43
|
],
|
|
38
44
|
"author": "Mike Valstar <mike@valstar.dev>",
|
|
39
45
|
"license": "MIT",
|
|
@@ -50,7 +56,8 @@
|
|
|
50
56
|
"typescript": "^5.1.6",
|
|
51
57
|
"vite": "^4.4.4",
|
|
52
58
|
"vite-plugin-dts": "^3.3.0",
|
|
53
|
-
"vitest": "^0.33.0"
|
|
59
|
+
"vitest": "^0.33.0",
|
|
60
|
+
"@tanstack/intent": "^0.0.23"
|
|
54
61
|
},
|
|
55
62
|
"jest": {
|
|
56
63
|
"collectCoverage": true,
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: core
|
|
3
|
+
description: >
|
|
4
|
+
Core caching with @gorgonjs/gorgon: Gorgon.get, Gorgon.put, Gorgon.clear
|
|
5
|
+
(with wildcards), Gorgon.overwrite, expiry policies, cache key naming,
|
|
6
|
+
concurrency deduplication, hooks, settings, custom storage providers via
|
|
7
|
+
IGorgonCacheProvider, @gorgonjs/file-provider for disk persistence, and
|
|
8
|
+
@gorgonjs/clearlink for distributed cache invalidation via WebSocket.
|
|
9
|
+
Use when caching API calls, database queries, or any async operation in
|
|
10
|
+
TypeScript/JavaScript.
|
|
11
|
+
type: core
|
|
12
|
+
library: gorgon
|
|
13
|
+
library_version: "1.6.0"
|
|
14
|
+
sources:
|
|
15
|
+
- "mikevalstar/gorgon:library/index.ts"
|
|
16
|
+
- "mikevalstar/gorgon:gorgonjs.dev/src/pages/docs/usage/get.md"
|
|
17
|
+
- "mikevalstar/gorgon:gorgonjs.dev/src/pages/docs/usage/policies.md"
|
|
18
|
+
- "mikevalstar/gorgon:gorgonjs.dev/src/pages/docs/concurrency.md"
|
|
19
|
+
- "mikevalstar/gorgon:gorgonjs.dev/src/pages/docs/custom-storage.md"
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
# Gorgon.js — Core Caching
|
|
23
|
+
|
|
24
|
+
Gorgon is a lightweight (~4kb, ~1.3kb gzipped) TypeScript caching library for async functions. It works in Node.js and browsers. Its key feature is automatic concurrency protection — multiple simultaneous requests for the same cache key are deduplicated, with all callers sharing the same result. Errors are never cached.
|
|
25
|
+
|
|
26
|
+
## Setup
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import Gorgon from '@gorgonjs/gorgon';
|
|
30
|
+
|
|
31
|
+
const user = await Gorgon.get(`user/${id}`, async () => {
|
|
32
|
+
const res = await fetch(`/api/users/${id}`);
|
|
33
|
+
return res.json();
|
|
34
|
+
}, 60 * 1000); // cache for 1 minute
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For React usage, see `react/SKILL.md`.
|
|
38
|
+
|
|
39
|
+
## Core Patterns
|
|
40
|
+
|
|
41
|
+
### Cache an async function with typed return
|
|
42
|
+
|
|
43
|
+
```typescript
|
|
44
|
+
import Gorgon from '@gorgonjs/gorgon';
|
|
45
|
+
|
|
46
|
+
interface User { id: number; name: string; email: string; }
|
|
47
|
+
|
|
48
|
+
const getUser = (id: number): Promise<User> =>
|
|
49
|
+
Gorgon.get(`user/${id}`, async () => {
|
|
50
|
+
const res = await fetch(`/api/users/${id}`);
|
|
51
|
+
return res.json();
|
|
52
|
+
}, 60 * 1000);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The return type flows through — `getUser` returns `Promise<User>` with full type safety.
|
|
56
|
+
|
|
57
|
+
### Cache key naming for wildcard invalidation
|
|
58
|
+
|
|
59
|
+
Use the format `type/id/sub-id` so you can clear groups with wildcards:
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
await Gorgon.get(`user/${id}/profile`, fetchProfile, 60000);
|
|
63
|
+
await Gorgon.get(`user/${id}/posts`, fetchPosts, 60000);
|
|
64
|
+
await Gorgon.get(`user/${id}/settings`, fetchSettings, 60000);
|
|
65
|
+
|
|
66
|
+
// Clear everything for a user at once
|
|
67
|
+
Gorgon.clear(`user/${id}/*`);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Directly insert or force-refresh cached data
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Insert a value directly
|
|
74
|
+
await Gorgon.put(`user/${id}`, userData, 60000);
|
|
75
|
+
|
|
76
|
+
// Force-refresh (always executes the function, no dedup)
|
|
77
|
+
const updated = await Gorgon.overwrite(`user/${id}`, async () => {
|
|
78
|
+
return fetch(`/api/users/${id}`).then(r => r.json());
|
|
79
|
+
}, 60000);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Expiry policies
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// Milliseconds from now
|
|
86
|
+
Gorgon.get('key', fn, 60000);
|
|
87
|
+
|
|
88
|
+
// Specific date
|
|
89
|
+
Gorgon.get('key', fn, new Date('2026-12-31'));
|
|
90
|
+
|
|
91
|
+
// Cache forever (use with caution — see Common Mistakes)
|
|
92
|
+
Gorgon.get('key', fn, false);
|
|
93
|
+
|
|
94
|
+
// No policy — cached until manually cleared
|
|
95
|
+
Gorgon.get('key', fn);
|
|
96
|
+
|
|
97
|
+
// Full policy object — specify provider and expiry
|
|
98
|
+
Gorgon.get('key', fn, { expiry: 60000, provider: 'file' });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Configure global settings
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
Gorgon.settings({
|
|
105
|
+
debug: true, // log cache hits/misses to console
|
|
106
|
+
defaultProvider: 'memory', // which storage provider to use
|
|
107
|
+
retry: 5000 // ms before a "stuck" request retries (default: 5000)
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Hook into cache lifecycle events
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
Gorgon.addHook('clear', (key, input, output) => {
|
|
115
|
+
console.log('Cache cleared:', input);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
Gorgon.addHook('valueError', (key, input, output) => {
|
|
119
|
+
console.error('Cache function threw:', output);
|
|
120
|
+
});
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Available events: `settings`, `addProvider`, `put`, `clear`, `clearAll`, `overwrite`, `get`, `valueError`.
|
|
124
|
+
|
|
125
|
+
### Custom storage providers
|
|
126
|
+
|
|
127
|
+
Implement `IGorgonCacheProvider` for Redis, IndexedDB, localStorage, etc.:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
import Gorgon, { IGorgonCacheProvider, GorgonPolicySanitized } from '@gorgonjs/gorgon';
|
|
131
|
+
|
|
132
|
+
const myProvider: IGorgonCacheProvider = {
|
|
133
|
+
init: async () => {},
|
|
134
|
+
get: async (key: string) => { /* return cached value or undefined */ },
|
|
135
|
+
set: async <R>(key: string, value: R, policy: GorgonPolicySanitized): Promise<R> => {
|
|
136
|
+
/* store value, policy.expiry is ms or false */
|
|
137
|
+
return value;
|
|
138
|
+
},
|
|
139
|
+
clear: async (key?: string) => { /* clear key or all */ return true; },
|
|
140
|
+
keys: async () => { /* return all keys */ return []; },
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
Gorgon.addProvider('my-provider', myProvider);
|
|
144
|
+
Gorgon.settings({ defaultProvider: 'my-provider' });
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### File provider — persist cache to disk (server-side)
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import Gorgon from '@gorgonjs/gorgon';
|
|
151
|
+
import { FileProvider } from '@gorgonjs/file-provider';
|
|
152
|
+
|
|
153
|
+
const fileCache = FileProvider('./cache', {
|
|
154
|
+
createSubfolder: false, // true creates a dated subfolder
|
|
155
|
+
clearFolder: false // true clears the directory on init
|
|
156
|
+
});
|
|
157
|
+
Gorgon.addProvider('file', fileCache);
|
|
158
|
+
|
|
159
|
+
const movie = await Gorgon.get(`movie/${id}`, async () => {
|
|
160
|
+
return fetch(`https://api.example.com/movie/${id}`).then(r => r.json());
|
|
161
|
+
}, { provider: 'file', expiry: false });
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
File provider uses `JSON.stringify` — only serializable data. For `Date`, `Set`, `Map`, use the `superjson` library.
|
|
165
|
+
|
|
166
|
+
### ClearLink — sync cache clearing across servers
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
// Server
|
|
170
|
+
import { server } from '@gorgonjs/clearlink';
|
|
171
|
+
server.init({ port: 8686 });
|
|
172
|
+
|
|
173
|
+
// Client (on each app instance)
|
|
174
|
+
import Gorgon from '@gorgonjs/gorgon';
|
|
175
|
+
import { client } from '@gorgonjs/clearlink';
|
|
176
|
+
|
|
177
|
+
client.connect('ws://127.0.0.1:8686');
|
|
178
|
+
client.apply(Gorgon); // hooks into clear and clearAll events
|
|
179
|
+
|
|
180
|
+
// Now Gorgon.clear() on any instance broadcasts to all others
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Only `clear` and `clearAll` are synced (not `put` or auto-expiry). Auto-reconnects after 10 seconds on disconnect.
|
|
184
|
+
|
|
185
|
+
## Common Mistakes
|
|
186
|
+
|
|
187
|
+
### CRITICAL Not clearing cache after mutating underlying data
|
|
188
|
+
|
|
189
|
+
Wrong:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
const user = await Gorgon.get(`user/${id}`, () => fetchUser(id), 60000);
|
|
193
|
+
await updateUser(id, newData);
|
|
194
|
+
// Forgot to clear — subsequent reads return stale data
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Correct:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
const user = await Gorgon.get(`user/${id}`, () => fetchUser(id), 60000);
|
|
201
|
+
await updateUser(id, newData);
|
|
202
|
+
Gorgon.clear(`user/${id}`);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Gorgon does not know when the underlying data changes. Always clear the relevant cache key after any mutation (POST, PUT, DELETE) that affects cached data.
|
|
206
|
+
|
|
207
|
+
Source: maintainer interview
|
|
208
|
+
|
|
209
|
+
### CRITICAL Cache keys not specific enough to input parameters
|
|
210
|
+
|
|
211
|
+
Wrong:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const results = await Gorgon.get('search-results', () =>
|
|
215
|
+
searchAPI(query, page, filters), 60000);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Correct:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
const results = await Gorgon.get(
|
|
222
|
+
`search/${query}/${page}/${JSON.stringify(filters)}`,
|
|
223
|
+
() => searchAPI(query, page, filters), 60000);
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
If the cache key does not include all varying parameters, different requests share the same cached result, returning wrong data silently.
|
|
227
|
+
|
|
228
|
+
Source: maintainer interview
|
|
229
|
+
|
|
230
|
+
### HIGH Caching forever without expiry in production
|
|
231
|
+
|
|
232
|
+
Wrong:
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
await Gorgon.get('config', fetchConfig, false);
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Correct:
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
await Gorgon.get('config', fetchConfig, 24 * 60 * 60 * 1000); // 1 day
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Even rarely-changing data should have an expiry. Permanent caches become stale silently and are hard to debug in production.
|
|
245
|
+
|
|
246
|
+
Source: maintainer interview
|
|
247
|
+
|
|
248
|
+
### MEDIUM Aligned cache expiry causing thundering herd
|
|
249
|
+
|
|
250
|
+
Wrong:
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
for (const id of popularIds) {
|
|
254
|
+
await Gorgon.get(`item/${id}`, () => fetchItem(id), 3600000);
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Correct:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
for (const id of popularIds) {
|
|
262
|
+
const fuzz = Math.random() * 600000; // up to 10 min variance
|
|
263
|
+
await Gorgon.get(`item/${id}`, () => fetchItem(id), 3600000 + fuzz);
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
When many items share the same TTL, they all expire simultaneously causing a burst of requests. Fuzz the expiry to spread cache refreshes over time.
|
|
268
|
+
|
|
269
|
+
Source: maintainer interview
|
|
270
|
+
|
|
271
|
+
### HIGH Using a plain Map or global object instead of Gorgon
|
|
272
|
+
|
|
273
|
+
Wrong:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
const cache = new Map();
|
|
277
|
+
async function getUser(id: number) {
|
|
278
|
+
if (cache.has(id)) return cache.get(id);
|
|
279
|
+
const user = await fetchUser(id);
|
|
280
|
+
cache.set(id, user);
|
|
281
|
+
return user;
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Correct:
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
import Gorgon from '@gorgonjs/gorgon';
|
|
289
|
+
|
|
290
|
+
const getUser = (id: number) =>
|
|
291
|
+
Gorgon.get(`user/${id}`, () => fetchUser(id), 60000);
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
A hand-rolled cache misses concurrency deduplication (10 simultaneous calls hit the API 10 times), has no expiry management, no wildcard clearing, and no type safety on cached returns.
|
|
295
|
+
|
|
296
|
+
Source: documentation — concurrency
|
|
297
|
+
|
|
298
|
+
### MEDIUM Wrapping Gorgon.get in custom deduplication logic
|
|
299
|
+
|
|
300
|
+
Wrong:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
const pending = new Map<string, Promise<any>>();
|
|
304
|
+
async function getUser(id: number) {
|
|
305
|
+
const key = `user/${id}`;
|
|
306
|
+
if (pending.has(key)) return pending.get(key);
|
|
307
|
+
const promise = Gorgon.get(key, () => fetchUser(id), 60000);
|
|
308
|
+
pending.set(key, promise);
|
|
309
|
+
const result = await promise;
|
|
310
|
+
pending.delete(key);
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Correct:
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
import Gorgon from '@gorgonjs/gorgon';
|
|
319
|
+
|
|
320
|
+
const getUser = (id: number) =>
|
|
321
|
+
Gorgon.get(`user/${id}`, () => fetchUser(id), 60000);
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Gorgon already deduplicates concurrent requests for the same key. External dedup is redundant and can introduce bugs with stale promise references.
|
|
325
|
+
|
|
326
|
+
Source: documentation — concurrency
|
|
327
|
+
|
|
328
|
+
### HIGH Tension: Cache duration vs data freshness
|
|
329
|
+
|
|
330
|
+
Longer cache times improve performance but increase risk of stale data. Agents optimizing for performance tend to set very long TTLs without an invalidation strategy; agents optimizing for correctness set very short TTLs, defeating the purpose of caching. Choose a TTL appropriate for the data's change frequency and always pair it with explicit `Gorgon.clear()` calls on mutation.
|
|
331
|
+
|
|
332
|
+
See also: `react/SKILL.md` § Common Mistakes
|
|
333
|
+
|
|
334
|
+
## Version
|
|
335
|
+
|
|
336
|
+
Targets @gorgonjs/gorgon v1.6.0.
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react
|
|
3
|
+
description: >
|
|
4
|
+
React integration for @gorgonjs/gorgon via @gorgonjs/react: useGorgon hook
|
|
5
|
+
with data/error/loading/refetch state, clearGorgon helper for cache
|
|
6
|
+
invalidation from components, cache key management tied to component
|
|
7
|
+
lifecycle, and DIY hook patterns. Use when building React components
|
|
8
|
+
that need cached async data fetching.
|
|
9
|
+
type: framework
|
|
10
|
+
library: gorgon
|
|
11
|
+
framework: react
|
|
12
|
+
library_version: "1.6.0"
|
|
13
|
+
requires:
|
|
14
|
+
- core
|
|
15
|
+
sources:
|
|
16
|
+
- "mikevalstar/gorgon:clients/react/index.ts"
|
|
17
|
+
- "mikevalstar/gorgon:gorgonjs.dev/src/pages/docs/ui/react.md"
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
This skill builds on `core/SKILL.md`. Read the core skill first for cache key naming, policies, and clearing patterns.
|
|
21
|
+
|
|
22
|
+
# Gorgon.js — React
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install @gorgonjs/react @gorgonjs/gorgon
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
import { useGorgon, clearGorgon } from '@gorgonjs/react';
|
|
32
|
+
|
|
33
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
34
|
+
const { data, error, loading, refetch } = useGorgon(
|
|
35
|
+
`user/${userId}`,
|
|
36
|
+
() => fetch(`/api/users/${userId}`).then(r => r.json()),
|
|
37
|
+
60 * 1000
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (loading) return <p>Loading...</p>;
|
|
41
|
+
if (error) return <p>Error: {error.message}</p>;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div>
|
|
45
|
+
<h1>{data.name}</h1>
|
|
46
|
+
<button onClick={() => refetch()}>Refresh</button>
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Hooks and Components
|
|
53
|
+
|
|
54
|
+
### useGorgon hook
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
const { data, error, loading, refetch } = useGorgon<R>(
|
|
58
|
+
key: string,
|
|
59
|
+
asyncFunc: () => Promise<R>,
|
|
60
|
+
policy?: GorgonPolicyInput,
|
|
61
|
+
options?: { debug?: boolean }
|
|
62
|
+
);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
- `data: R | null` — resolved data, null while loading
|
|
67
|
+
- `error: Error | null` — error if the async function threw
|
|
68
|
+
- `loading: boolean` — true while fetching
|
|
69
|
+
- `refetch(opts?: { clearKey?: string }): void` — clears cache key and re-fetches
|
|
70
|
+
|
|
71
|
+
The hook re-fetches when `key` changes. Include all dynamic parameters in the key.
|
|
72
|
+
|
|
73
|
+
### clearGorgon helper
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { clearGorgon } from '@gorgonjs/react';
|
|
77
|
+
|
|
78
|
+
clearGorgon('user/*'); // clear keys matching pattern
|
|
79
|
+
clearGorgon(); // clear all cached data
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Refetch with wildcard clearing
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const { data, refetch } = useGorgon(
|
|
86
|
+
`user/${userId}`,
|
|
87
|
+
() => fetchUser(userId),
|
|
88
|
+
60000
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
// Clear just this key and re-fetch
|
|
92
|
+
const handleRefresh = () => refetch();
|
|
93
|
+
|
|
94
|
+
// Clear all user keys and re-fetch this one
|
|
95
|
+
const handleClearAll = () => refetch({ clearKey: 'user/*' });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## React-Specific Patterns
|
|
99
|
+
|
|
100
|
+
### Typed data fetching in components
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { useGorgon } from '@gorgonjs/react';
|
|
104
|
+
|
|
105
|
+
interface Todo { id: number; title: string; completed: boolean; }
|
|
106
|
+
|
|
107
|
+
function TodoItem({ id }: { id: number }) {
|
|
108
|
+
const { data, loading } = useGorgon<Todo>(
|
|
109
|
+
`todo/${id}`,
|
|
110
|
+
() => fetch(`/api/todos/${id}`).then(r => r.json()),
|
|
111
|
+
30000
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (loading || !data) return <span>Loading...</span>;
|
|
115
|
+
return <span>{data.title}</span>;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Invalidate on mutation
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
function EditUser({ userId }: { userId: string }) {
|
|
123
|
+
const { data, refetch } = useGorgon(
|
|
124
|
+
`user/${userId}`,
|
|
125
|
+
() => fetchUser(userId),
|
|
126
|
+
60000
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const handleSave = async (formData: UserUpdate) => {
|
|
130
|
+
await updateUser(userId, formData);
|
|
131
|
+
refetch(); // clears cache and re-renders with fresh data
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return <UserForm data={data} onSave={handleSave} />;
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### DIY minimal hook (without @gorgonjs/react)
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { useState, useEffect } from 'react';
|
|
142
|
+
import Gorgon, { GorgonPolicyInput } from '@gorgonjs/gorgon';
|
|
143
|
+
|
|
144
|
+
function useGorgon<R>(
|
|
145
|
+
key: string,
|
|
146
|
+
asyncFunc: () => Promise<R>,
|
|
147
|
+
policy?: GorgonPolicyInput
|
|
148
|
+
): R | null {
|
|
149
|
+
const [data, setData] = useState<R | null>(null);
|
|
150
|
+
|
|
151
|
+
useEffect(() => {
|
|
152
|
+
let mounted = true;
|
|
153
|
+
Gorgon.get(key, asyncFunc, policy)
|
|
154
|
+
.then(result => { if (mounted) setData(result); })
|
|
155
|
+
.catch(err => console.error('Gorgon error', err));
|
|
156
|
+
return () => { mounted = false; };
|
|
157
|
+
}, [key]);
|
|
158
|
+
|
|
159
|
+
return data;
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Common Mistakes
|
|
164
|
+
|
|
165
|
+
### CRITICAL Not including all dynamic params in the cache key
|
|
166
|
+
|
|
167
|
+
Wrong:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
const { data } = useGorgon('user-profile', () => fetchUser(userId), 60000);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Correct:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const { data } = useGorgon(`user/${userId}`, () => fetchUser(userId), 60000);
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
`useGorgon` re-fetches when the key changes. If dynamic parameters are not in the key, changing `userId` returns stale data from the previous user.
|
|
180
|
+
|
|
181
|
+
Source: documentation — react
|
|
182
|
+
|
|
183
|
+
### HIGH Using Gorgon.clear directly instead of refetch
|
|
184
|
+
|
|
185
|
+
Wrong:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
const { data } = useGorgon('user/1', fetchUser, 60000);
|
|
189
|
+
const handleUpdate = async () => {
|
|
190
|
+
await updateUser(1, newData);
|
|
191
|
+
Gorgon.clear('user/1'); // cache is cleared but component doesn't re-render
|
|
192
|
+
};
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Correct:
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
const { data, refetch } = useGorgon('user/1', fetchUser, 60000);
|
|
199
|
+
const handleUpdate = async () => {
|
|
200
|
+
await updateUser(1, newData);
|
|
201
|
+
refetch(); // clears cache AND triggers re-render
|
|
202
|
+
};
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
`Gorgon.clear()` removes the cached value but does not trigger a React re-render. Use `refetch()` from the hook to clear and re-fetch in one step.
|
|
206
|
+
|
|
207
|
+
Source: documentation — react
|
|
208
|
+
|
|
209
|
+
### HIGH Missing cleanup in DIY hooks
|
|
210
|
+
|
|
211
|
+
Wrong:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
useEffect(() => {
|
|
215
|
+
Gorgon.get(key, asyncFunc, policy).then(setData);
|
|
216
|
+
}, [key]);
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Correct:
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
useEffect(() => {
|
|
223
|
+
let mounted = true;
|
|
224
|
+
Gorgon.get(key, asyncFunc, policy)
|
|
225
|
+
.then(result => { if (mounted) setData(result); });
|
|
226
|
+
return () => { mounted = false; };
|
|
227
|
+
}, [key]);
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Without the mounted guard, setting state after unmount causes React warnings and potential bugs with rapid navigation.
|
|
231
|
+
|
|
232
|
+
Source: documentation — react
|
|
233
|
+
|
|
234
|
+
### HIGH Tension: Cache duration vs data freshness
|
|
235
|
+
|
|
236
|
+
When using `useGorgon`, the same tension from core caching applies in the UI: long TTLs show stale data to users, short TTLs cause excessive re-fetching and loading spinners. Pair appropriate TTLs with explicit `refetch()` calls on user-initiated mutations.
|
|
237
|
+
|
|
238
|
+
See also: `core/SKILL.md` § Common Mistakes
|
|
239
|
+
|
|
240
|
+
## Version
|
|
241
|
+
|
|
242
|
+
Targets @gorgonjs/react with @gorgonjs/gorgon v1.6.0.
|