Cachetta for TypeScript
File-based caching for TypeScript. Uses v8.serialize for native binary serialization – any file extension works, and all V8-serializable types (Maps, Sets, Dates, Buffers, typed arrays, RegExps, etc.) are supported natively.
Install
pnpm add cachetta
Basic Usage
Create a cache object:
import { Cachetta } from 'cachetta';
const cache = new Cachetta({
read: true,
write: true,
path: './cache.json',
duration: 24 * 60 * 60 * 1000, // 1 day in milliseconds
});
Read and write:
import { readCache, writeCache } from 'cachetta';
async function getData() {
const cachedData = await readCache(cache);
if (cachedData) {
return cachedData;
}
const data = await fetchData();
await writeCache(cache, data);
return data;
}
Decorators
Use Cachetta as a decorator (requires experimental decorators):
import { Cachetta } from 'cachetta';
class DataService {
@Cachetta({ path: '/my-cache.json' })
async getData() {
return await fetchData();
}
}
With a specific cache object:
const cache = new Cachetta({ path: '/my-cache.json' });
class DataService {
@cache
async getData() {
return await fetchData();
}
}
Or with overrides:
const cache = new Cachetta({ path: '/my-cache.json' });
class DataService {
@cache({ duration: 1000 })
async getData() {
return await fetchData();
}
}
Decorated functions always return Promises, even if the original function is synchronous. Always use
awaitwhen calling decorated functions.
Function Wrapper
If you’re not using decorators, wrap functions manually:
const cache = new Cachetta({ path: './my-cache.json' });
const cachedGetData = cache(async () => {
return await fetchData();
});
const result = await cachedGetData();
With configuration:
const cache = new Cachetta({ path: './cache' });
const cachedGetData = cache(getData, {
path: (id) => `./cache/data-${id}.json`,
duration: 5000
});
const result = await cachedGetData(123);
Sync API
All methods have synchronous counterparts:
import { Cachetta, writeCacheSync, readCacheSync } from 'cachetta';
const cache = new Cachetta({ path: './cache.json' });
// Sync read/write
writeCacheSync(cache, { data: 1 });
const data = readCacheSync(cache);
// Sync inspection
cache.existsSync();
cache.ageSync();
cache.infoSync();
// Sync invalidation
cache.invalidateSync();
// Sync function wrapping
const cachedFn = cache.wrapSync(() => computeExpensiveValue());
const result = cachedFn();
Auto Cache Keys
When a wrapped function receives arguments, Cachetta automatically generates unique cache paths by hashing the arguments:
const cache = new Cachetta({ path: './cache/users.json' });
const getUser = cache((userId) => fetchUser(userId));
await getUser(1); // cached at ./cache/users-<hash1>.json
await getUser(2); // cached at ./cache/users-<hash2>.json
In-Memory LRU
Add an in-memory LRU layer that is checked before hitting disk:
const cache = new Cachetta({
path: './cache.json',
lruSize: 100,
});
LRU entries respect the same duration as disk entries and use lazy expiration.
Conditional Caching
Cache results only when a condition function returns true:
const cache = new Cachetta({
path: './cache.json',
condition: (result) => result !== null,
});
Stale-While-Revalidate
Return expired data immediately while refreshing in the background:
const cache = new Cachetta({
path: './cache.json',
duration: 60 * 60 * 1000, // 1 hour
staleDuration: 30 * 60 * 1000, // serve stale up to 30min past expiry
});
Cache Invalidation
const cache = new Cachetta({ path: './cache.json' });
await cache.invalidate(); // or cache.clear()
cache.invalidateSync(); // sync variant
// With arguments (when using path functions)
await cache.invalidate('userId');
Cache Inspection
Query cache state without reading the cached data:
const cache = new Cachetta({ path: './cache.json' });
await cache.exists(); // true if the cache file exists
await cache.age(); // age in milliseconds, or null
await cache.info(); // { exists, age, expired, stale, path }
// Sync variants
cache.existsSync();
cache.ageSync();
cache.infoSync();
Dynamic Cache Paths
Specify a function for defining the path:
function getCachePath(n) {
return `./cache/${n}.json`;
}
@Cachetta({ path: getCachePath })
async function foo(n) {
return computeExpensiveValue(n);
}
Specifying Paths
Use copy to create variations of a cache configuration:
const cache = new Cachetta({ path: './cache' });
const newCache = cache.copy({
read: false,
duration: 2 * 24 * 60 * 60 * 1000,
});
Error Handling
Cachetta gracefully handles corrupt cache files by returning null:
const cache = new Cachetta({ path: './cache.json' });
const data = await readCache(cache);
if (data === null) {
// Cache is missing or corrupt
const freshData = await fetchFreshData();
await writeCache(cache, freshData);
}
Logging
import { setLogLevel, setLogger } from 'cachetta';
// Enable debug logging
setLogLevel('debug'); // 'error', 'warn', 'info', 'debug'
// Or use a custom logger
setLogger({
debug: (msg) => myLogger.debug(msg),
info: (msg) => myLogger.info(msg),
warn: (msg) => myLogger.warn(msg),
error: (msg) => myLogger.error(msg),
});
Configuration Reference
| Option | Type | Default | Description |
|---|---|---|---|
path | string \| Function | required | Cache file path or path function |
read | boolean | true | Allow reading from cache |
write | boolean | true | Allow writing to cache |
duration | number | 7 days (ms) | Cache TTL in milliseconds |
lruSize | number | undefined | Max in-memory LRU entries |
condition | Function | undefined | Predicate to decide whether to cache |
staleDuration | number | undefined | Time past expiry to serve stale data |