| name | core |
| description | Core caching with @gorgonjs/gorgon: Gorgon.get, Gorgon.put, Gorgon.clear (with wildcards), Gorgon.overwrite, expiry policies, cache key naming, concurrency deduplication, hooks, settings, custom storage providers via IGorgonCacheProvider, @gorgonjs/file-provider for disk persistence, and @gorgonjs/clearlink for distributed cache invalidation via WebSocket. Use when caching API calls, database queries, or any async operation in TypeScript/JavaScript.
|
| type | core |
| library | gorgon |
| library_version | 1.6.0 |
| sources | ["mikevalstar/gorgon:library/index.ts","mikevalstar/gorgon:gorgonjs.dev/src/pages/docs/usage/get.md","mikevalstar/gorgon:gorgonjs.dev/src/pages/docs/usage/policies.md","mikevalstar/gorgon:gorgonjs.dev/src/pages/docs/concurrency.md","mikevalstar/gorgon:gorgonjs.dev/src/pages/docs/custom-storage.md"] |
Gorgon.js — Core Caching
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.
Setup
import Gorgon from '@gorgonjs/gorgon';
const user = await Gorgon.get(`user/${id}`, async () => {
const res = await fetch(`/api/users/${id}`);
return res.json();
}, 60 * 1000);
For React usage, see react/SKILL.md.
Core Patterns
Cache an async function with typed return
import Gorgon from '@gorgonjs/gorgon';
interface User { id: number; name: string; email: string; }
const getUser = (id: number): Promise<User> =>
Gorgon.get(`user/${id}`, async () => {
const res = await fetch(`/api/users/${id}`);
return res.json();
}, 60 * 1000);
The return type flows through — getUser returns Promise<User> with full type safety.
Cache key naming for wildcard invalidation
Use the format type/id/sub-id so you can clear groups with wildcards:
await Gorgon.get(`user/${id}/profile`, fetchProfile, 60000);
await Gorgon.get(`user/${id}/posts`, fetchPosts, 60000);
await Gorgon.get(`user/${id}/settings`, fetchSettings, 60000);
Gorgon.clear(`user/${id}/*`);
Directly insert or force-refresh cached data
await Gorgon.put(`user/${id}`, userData, 60000);
const updated = await Gorgon.overwrite(`user/${id}`, async () => {
return fetch(`/api/users/${id}`).then(r => r.json());
}, 60000);
Expiry policies
Gorgon.get('key', fn, 60000);
Gorgon.get('key', fn, new Date('2026-12-31'));
Gorgon.get('key', fn, false);
Gorgon.get('key', fn);
Gorgon.get('key', fn, { expiry: 60000, provider: 'file' });
Configure global settings
Gorgon.settings({
debug: true,
defaultProvider: 'memory',
retry: 5000
});
Hook into cache lifecycle events
Gorgon.addHook('clear', (key, input, output) => {
console.log('Cache cleared:', input);
});
Gorgon.addHook('valueError', (key, input, output) => {
console.error('Cache function threw:', output);
});
Available events: settings, addProvider, put, clear, clearAll, overwrite, get, valueError.
Custom storage providers
Implement IGorgonCacheProvider for Redis, IndexedDB, localStorage, etc.:
import Gorgon, { IGorgonCacheProvider, GorgonPolicySanitized } from '@gorgonjs/gorgon';
const myProvider: IGorgonCacheProvider = {
init: async () => {},
get: async (key: string) => { },
set: async <R>(key: string, value: R, policy: GorgonPolicySanitized): Promise<R> => {
return value;
},
clear: async (key?: string) => { return true; },
keys: async () => { return []; },
};
Gorgon.addProvider('my-provider', myProvider);
Gorgon.settings({ defaultProvider: 'my-provider' });
File provider — persist cache to disk (server-side)
import Gorgon from '@gorgonjs/gorgon';
import { FileProvider } from '@gorgonjs/file-provider';
const fileCache = FileProvider('./cache', {
createSubfolder: false,
clearFolder: false
});
Gorgon.addProvider('file', fileCache);
const movie = await Gorgon.get(`movie/${id}`, async () => {
return fetch(`https://api.example.com/movie/${id}`).then(r => r.json());
}, { provider: 'file', expiry: false });
File provider uses JSON.stringify — only serializable data. For Date, Set, Map, use the superjson library.
ClearLink — sync cache clearing across servers
import { server } from '@gorgonjs/clearlink';
server.init({ port: 8686 });
import Gorgon from '@gorgonjs/gorgon';
import { client } from '@gorgonjs/clearlink';
client.connect('ws://127.0.0.1:8686');
client.apply(Gorgon);
Only clear and clearAll are synced (not put or auto-expiry). Auto-reconnects after 10 seconds on disconnect.
Common Mistakes
CRITICAL Not clearing cache after mutating underlying data
Wrong:
const user = await Gorgon.get(`user/${id}`, () => fetchUser(id), 60000);
await updateUser(id, newData);
Correct:
const user = await Gorgon.get(`user/${id}`, () => fetchUser(id), 60000);
await updateUser(id, newData);
Gorgon.clear(`user/${id}`);
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.
Source: maintainer interview
CRITICAL Cache keys not specific enough to input parameters
Wrong:
const results = await Gorgon.get('search-results', () =>
searchAPI(query, page, filters), 60000);
Correct:
const results = await Gorgon.get(
`search/${query}/${page}/${JSON.stringify(filters)}`,
() => searchAPI(query, page, filters), 60000);
If the cache key does not include all varying parameters, different requests share the same cached result, returning wrong data silently.
Source: maintainer interview
HIGH Caching forever without expiry in production
Wrong:
await Gorgon.get('config', fetchConfig, false);
Correct:
await Gorgon.get('config', fetchConfig, 24 * 60 * 60 * 1000);
Even rarely-changing data should have an expiry. Permanent caches become stale silently and are hard to debug in production.
Source: maintainer interview
MEDIUM Aligned cache expiry causing thundering herd
Wrong:
for (const id of popularIds) {
await Gorgon.get(`item/${id}`, () => fetchItem(id), 3600000);
}
Correct:
for (const id of popularIds) {
const fuzz = Math.random() * 600000;
await Gorgon.get(`item/${id}`, () => fetchItem(id), 3600000 + fuzz);
}
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.
Source: maintainer interview
HIGH Using a plain Map or global object instead of Gorgon
Wrong:
const cache = new Map();
async function getUser(id: number) {
if (cache.has(id)) return cache.get(id);
const user = await fetchUser(id);
cache.set(id, user);
return user;
}
Correct:
import Gorgon from '@gorgonjs/gorgon';
const getUser = (id: number) =>
Gorgon.get(`user/${id}`, () => fetchUser(id), 60000);
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.
Source: documentation — concurrency
MEDIUM Wrapping Gorgon.get in custom deduplication logic
Wrong:
const pending = new Map<string, Promise<any>>();
async function getUser(id: number) {
const key = `user/${id}`;
if (pending.has(key)) return pending.get(key);
const promise = Gorgon.get(key, () => fetchUser(id), 60000);
pending.set(key, promise);
const result = await promise;
pending.delete(key);
return result;
}
Correct:
import Gorgon from '@gorgonjs/gorgon';
const getUser = (id: number) =>
Gorgon.get(`user/${id}`, () => fetchUser(id), 60000);
Gorgon already deduplicates concurrent requests for the same key. External dedup is redundant and can introduce bugs with stale promise references.
Source: documentation — concurrency
HIGH Tension: Cache duration vs data freshness
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.
See also: react/SKILL.md § Common Mistakes
Version
Targets @gorgonjs/gorgon v1.6.0.