@sunacchi/cache-kit
v1.0.2
Published
Universal cache library with pluggable adapters for Node, Bun, Deno, and browsers
Downloads
69
Maintainers
Readme
@sunacchi/cache-kit
Core cache library with a unified async API, pluggable adapters, and built-in MemoryAdapter.
Part of the Cache Kit monorepo.
Install
npm install @sunacchi/cache-kitQuick Start
import { Cache } from '@sunacchi/cache-kit';
const cache = new Cache<string>();
await cache.set('key', 'value');
const val = await cache.get('key'); // 'value'Constructor Options
const cache = new Cache<MyType>({
adapter: new MemoryAdapter(), // default — swap for any CacheAdapter
defaultTtl: 60_000, // ms, undefined = no expiry
staleWhileRevalidate: 30_000,// ms grace period after TTL
maxSize: 1000, // max entries before eviction
eviction: 'lru', // 'lru' | 'fifo'
namespace: 'users', // key prefix
serialize: (entry) => JSON.stringify(entry),
deserialize: (raw) => JSON.parse(raw),
middleware: [], // initial middleware stack
});API
Core Methods
| Method | Returns | Description |
|--------|---------|-------------|
| get(key) | Promise<T \| undefined> | Retrieve a value. Returns undefined on miss or expiry. |
| set(key, value, ttl?, swr?) | Promise<void> | Store a value with optional per-call TTL and SWR. |
| has(key) | Promise<boolean> | Check existence (respects TTL). |
| delete(key) | Promise<boolean> | Remove a key. |
| clear() | Promise<void> | Remove all entries. |
| keys() | AsyncIterable<string> | Iterate over stored keys. |
| size() | Promise<number> | Count of stored entries. |
Read-Through
// Single key — factory only called on miss, with stampede protection
const user = await cache.getOrCreate('user:42', async () => {
return await db.users.findById(42);
}, { ttl: 30_000 });
// Batch — only missed keys hit the factory
const users = await cache.getOrCreateMany(
['user:1', 'user:2', 'user:3'],
async (missed) => {
const rows = await db.users.findByIds(missed);
return new Map(rows.map(u => [u.id, u]));
},
);
// Function wrapper — transparent caching
const getUser = cache.wrap(
(id: number) => `user:${id}`,
(id: number) => db.users.findById(id),
{ ttl: 60_000 },
);Events
cache.on('hit', ({ key, value }) => { /* ... */ });
cache.on('miss', ({ key }) => { /* ... */ });
cache.on('set', ({ key, value }) => { /* ... */ });
cache.on('delete', ({ key }) => { /* ... */ });
cache.on('evict', ({ key, reason }) => { /* ... */ });
cache.on('stale', ({ key, value }) => { /* ... */ });
const unsub = cache.on('hit', handler);
unsub(); // unsubscribeStatistics
const s = cache.stats();
// { hits, misses, sets, deletes, evictions, staleHits, hitRate }
cache.resetStats();Middleware
cache.use({
before(ctx) {
// ctx: { key, operation, value?, ttl? }
return true; // false aborts the operation
},
after(ctx, result) {
// runs after the operation completes
},
});MemoryAdapter
The default adapter. Uses a Map<string, string> under the hood — synchronous, zero-config.
import { MemoryAdapter } from '@sunacchi/cache-kit';
const adapter = new MemoryAdapter();Writing a Custom Adapter
Implement the CacheAdapter interface. Adapters store and return strings — serialization is handled by the Cache class.
import type { CacheAdapter } from '@sunacchi/cache-kit';
export class MyAdapter implements CacheAdapter {
async get(key: string): Promise<string | undefined> { /* ... */ }
async set(key: string, value: string, ttlMs?: number): Promise<void> { /* ... */ }
async delete(key: string): Promise<boolean> { /* ... */ }
async clear(): Promise<void> { /* ... */ }
async has(key: string): Promise<boolean> { /* ... */ }
async *keys(): AsyncIterable<string> { /* ... */ }
async size(): Promise<number> { /* ... */ }
}Exports
// Classes
export { Cache } from './cache.js';
export { MemoryAdapter } from './memory-adapter.js';
export { defaultSerialize, defaultDeserialize } from './serialization.js';
// Types
export type {
CacheAdapter,
CacheEntry,
CacheOptions,
GetOrCreateOptions,
CacheEventType,
CacheEventMap,
CacheListener,
CacheStats,
CacheMiddleware,
MiddlewareContext,
} from './types.js';License
MIT
