@honico/cache
v1.7.0
Published
Injectable named caches for Honico using cache-manager and Keyv
Readme
@honico/cache
Application-scoped, injectable registry of named caches backed by the official
cache-manager v7. Each lookup returns its original Cache instance, including
get, set, mget, mset, del, mdel, clear, ttl, wrap, events,
stores, and disconnect.
App configuration
Install @honico/cache and the Keyv adapters your app uses, such as @keyv/redis.
import { defineConfig } from '@honico/core';
import { cachePlugin, createInMemoryCache } from '@honico/cache';
import { createKeyv as createRedisCache } from '@keyv/redis';
export default defineConfig({
autoload: { providers: ['src/**/*.service.ts'] },
plugins: [cachePlugin],
cache: {
local: () => ({ stores: [createInMemoryCache()] }),
users: (name) => ({
stores: [createRedisCache(process.env.REDIS_URL!, {
namespace: `my-app:${name}`,
})],
}),
layered: (name) => ({
stores: [
createInMemoryCache(),
createRedisCache(process.env.REDIS_URL!, { namespace: `my-app:${name}` }),
],
}),
},
});Cache names are unique object keys and must not be blank. Each value accepts the
CacheOptions (upstream options with a required, non-empty stores list), or a synchronous/asynchronous factory receiving
the name and returning those options. Factories run once during plugin setup;
use them for adapters so build/type-generation tools can load configuration
without opening network connections. Factories that fail before returning must
clean up any resources they created. Previously created caches are disconnected
if a later factory fails.
Startup readiness
The manager checks every store before registration, including every layer in a
cache. Redis clients exposed by @keyv/redis are connected and checked with
PING; Map-backed memory stores are immediately ready. Startup rejects on a
connection error, authentication error, failed ping, or timeout. Redis reconnect
loops are stopped on failure, and initialized caches are disconnected. Startup
logs report ready only after all configured caches pass their checks.
startupTimeout sets the timeout per store in milliseconds (default: 5_000).
For other adapters, provide startupCheck; Keyv has no universal readiness API.
This callback is invoked for each store and overrides the built-in check:
custom: () => ({
stores: [createMyKeyvStore()],
startupTimeout: 3_000,
startupCheck: async (store, signal) => {
await verifyMyAdapterConnection(store, signal);
},
})The callback must reject on failure and honor the abort signal to stop pending
work. It must not rely on a cache read that silently turns connection errors into
cache misses. Redis PING verifies connectivity and authentication, not write
permissions or readiness of every cluster node. There is no automatic degraded
startup or memory fallback after a readiness failure.
Every named cache must declare at least one Keyv store. Missing stores and
empty arrays are rejected by TypeScript and at runtime, including factory
results. For memory caching, explicitly use
stores: [createInMemoryCache()]. An empty cache registry is allowed.
All Keyv-compatible adapters are supported through stores: [new Keyv({ store:
adapter })], or an adapter's official createKeyv helper. No adapter whitelist
or Redis-specific configuration is imposed. Install adapters in the consuming
app; Redis is not required by this package.
Give each cache its own Keyv/adapter instances and a distinct namespace when
sharing a backend. Registry names do not rewrite adapter namespaces. This
preserves adapter-specific configuration and prevents one cache's keys or
clear() from affecting another. Follow the adapter's namespace/clear semantics.
Leave cacheId unset unless you specifically need to override upstream's unique
ID; reusing IDs can coalesce concurrent wrap calls between caches.
Injection
import { Inject, Provide } from '@honico/core';
import { CacheManager } from '@honico/cache';
@Provide()
export class UserService {
@Inject(CacheManager)
cacheManager!: CacheManager;
getUser(id: string) {
return this.cacheManager.getCache('users').wrap(
`user:${id}`,
async () => ({ id }), // Replace with the database lookup
60_000, // Per-entry TTL in milliseconds
);
}
}CacheManager is shared across requests in one app, not across app instances.
getCache(name) returns the same instance each time and throws for unknown
names. No implicit default cache is created. TTLs are milliseconds; cache
misses return undefined. Error/event behavior follows cache-manager and the
selected Keyv adapter without an additional wrapper.
Specify TTLs per entry where the data's freshness requirements are known:
await cache.set('user:123', user, 60_000) or
await cache.wrap('user:123', loadUser, 60_000). App configuration and examples
omit default TTLs. The upstream default TTL option remains available, but is
not required. Without a per-entry TTL or a cache/adapter default, entries have
no time-based expiration.
For manual use, call await CacheManager.create(configuration). Call
await manager.close() during your application's shutdown to disconnect all
managed caches without clearing their data. Closing is idempotent and attempts
every cache even if one fails. Honico currently has no plugin shutdown hook, so
the package does not install process signal handlers. Do not share owned
connections with unrelated services that must remain open after closing.
The example API configures local with memory and shared with @keyv/redis.
REDIS_URL selects the Redis server (the adapter defaults to localhost when
omitted). A CacheExampleService
demonstrates injection. Run package build, typecheck and test scripts with
npm run <script> --workspace=@honico/cache.
