stash
Problem
You need a typed cache with TTL expiry, stampede prevention for async factories, and an eviction callback — beyond what a plain Map provides.
Solution
Use stash(options?) to create a Stash<T, K> instance. hash defaults to String(key) so no options are needed for string keys.
ts
import { stash } from '@vielzeug/arsenal';
// or: import { stash } from '@vielzeug/arsenal/cache';
// Simple string-keyed cache — no options required
const sessionCache = stash<User>();
sessionCache.set('user:1', { id: 1, name: 'Alice' }, { ttlMs: 30_000 });
sessionCache.get('user:1'); // { id: 1, name: 'Alice' }Global TTL and bounded size
ts
import { stash } from '@vielzeug/arsenal';
const apiCache = stash<Response>({
ttlMs: 60_000, // all entries expire after 60s unless overridden per set()
maxSize: 500, // FIFO eviction when exceeded
onEvict: (key, value) => console.log('evicted', key),
});
apiCache.set('data', response); // expires in 60s
apiCache.set('data', response, { ttlMs: 5_000 }); // override: 5sNon-string keys
ts
import { stash } from '@vielzeug/arsenal';
const userCache = stash<User, readonly [string, number]>({
hash: (key) => JSON.stringify(key),
});
userCache.set(['user', 1], { id: 1, name: 'Alice' });
userCache.get(['user', 1]); // { id: 1, name: 'Alice' }Async getOrSet with stampede prevention
ts
import { stash } from '@vielzeug/arsenal';
const cache = stash<User>();
// Concurrent calls with the same key share one in-flight Promise
const [a, b] = await Promise.all([
cache.getOrSet('user-1', () => fetchUser(1)),
cache.getOrSet('user-1', () => fetchUser(1)), // reuses the first in-flight call
]);
// Force a fresh fetch, bypass the cache
const fresh = await cache.getOrSet('user-1', () => fetchUser(1), { forceRefresh: true });Pitfalls
getOrSetcachesundefined— if the factory returnsundefined, subsequent calls returnundefinedwithout calling the factory again.- TTL expiry is checked lazily on read — expired entries are not purged until accessed.
- For persistent caches (localStorage, IPC), pass
{ persistence: { serialize, deserialize } }as a paired object — both fields are required together.