
Security News
GitHub Actions Checkout Now Blocks Risky pull_request_target Checkouts
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.
@fluojs/cache-manager
Advanced tools
Decorator-driven HTTP response caching and standalone cache service for Fluo, with memory and Redis backends.
English 한국어
General-purpose cache manager for fluo with pluggable memory, Redis, and custom store adapters. Provides both decorator-driven HTTP response caching and a standalone cache API for application-level caching.
npm install @fluojs/cache-manager
The root @fluojs/cache-manager import stays safe for memory-only installs. You only need Redis peers when you explicitly select the Redis-backed store path.
For Redis-backed caching:
npm install @fluojs/cache-manager @fluojs/redis ioredis
Register the CacheModule and use the CacheInterceptor on your controllers.
The built-in memory path is intentionally bounded by default: when you omit ttl, fluo applies a 300-second default TTL and keeps at most 1,000 live memory-store entries before evicting the oldest keys.
import { Module } from '@fluojs/core';
import { Controller, Get, UseInterceptors } from '@fluojs/http';
import { CacheModule, CacheInterceptor, CacheTTL } from '@fluojs/cache-manager';
@Controller('/products')
class ProductController {
@Get('/')
@UseInterceptors(CacheInterceptor)
@CacheTTL(60) // Cache for 60 seconds
list() {
return [{ id: 1, name: 'Product A' }];
}
}
@Module({
imports: [CacheModule.forRoot({ store: 'memory' })],
controllers: [ProductController],
})
class AppModule {}
Inject CacheService to manage cache programmatically.
import { Inject } from '@fluojs/core';
import { CacheService } from '@fluojs/cache-manager';
@Inject(CacheService)
class UserService {
constructor(private readonly cache: CacheService) {}
async getProfile(userId: string) {
return this.cache.remember(`user:${userId}`, async () => {
// This runs only if the key is missing from cache
return fetchUserProfile(userId);
}, 300); // 5 minutes
}
}
To use Redis, ensure @fluojs/redis is configured and set the store to 'redis'.
Memory-only consumers can keep importing from @fluojs/cache-manager without installing @fluojs/redis or ioredis; those optional peers are resolved only when the Redis store path is selected.
CacheModule.forRoot({
store: 'redis',
ttl: 600,
keyPrefix: 'myapp:cache:',
})
If you registered multiple Redis clients, set redis.clientName to target a named @fluojs/redis connection.
Leave redis.clientName unset to keep using the default Redis client resolved through REDIS_CLIENT.
CacheModule.forRoot({
store: 'redis',
redis: { clientName: 'cache' },
})
redis.client remains the highest-precedence override. Use it only when you need to bypass DI-based client selection entirely.
The built-in RedisStore persists entries with JSON.stringify(...). Cache values therefore need to be JSON-compatible: plain objects, arrays, strings, numbers, booleans, and null round-trip cleanly, while values such as Date come back as JSON output (for example ISO strings), functions/undefined/symbols do not survive, and non-serializable values like bigint or cyclic graphs should be normalized before caching.
Positive Redis TTL values are accepted in seconds and may be fractional. Redis expiry is rounded up to the next whole second because Redis EX uses integer seconds, while fluo also records the millisecond-precision expiry timestamp in the stored entry and treats the value as expired once that timestamp is reached. Use ttl: 0 when you intentionally want no Redis expiry.
Redis reset ownership is scoped by the top-level keyPrefix option, which defaults to fluo:cache: and is passed through to the built-in RedisStore namespace. CacheService.reset() deletes only keys under that prefix for Redis-backed stores, so application-owned Redis data outside the cache prefix is preserved. If you intentionally configure an empty keyPrefix, reset is limited to keys written by the current RedisStore instance instead of scanning *; use a non-empty, application-specific prefix when you need reset to cover cache entries across restarts or multiple processes.
Built-in HTTP cache key strategies derive their path segment from the concrete request path (requestContext.request.path), not the route template metadata. That means requests such as /users/1 and /users/2 always resolve to different cache keys even when they hit the same @Get('/:id') handler.
By default, anonymous requests use the concrete request path and ignore query parameters. Authenticated requests append a principal scope when one is available; use principalScopeResolver to customize that suffix. Enable httpKeyStrategy: 'route+query' (or full, which is equivalent for the built-in strategy set) to cache different responses for different search parameters. Query-aware keys canonicalize both parameter names and repeated values, so /products?tag=a&tag=b and /products?tag=b&tag=a share one cache entry.
CacheModule.forRoot({
store: 'memory',
httpKeyStrategy: 'route+query',
})
For fully custom keying, pass a function as httpKeyStrategy or use @CacheKey(...) with either a literal key or a key factory. These function-based hooks are the supported extension path for request-aware keys; do not subclass CacheInterceptor just to replace cache-key generation.
CacheModule.forRoot({
store: 'memory',
httpKeyStrategy: (context) => {
const path = context.requestContext.request.path;
const query = context.requestContext.request.query;
const q = String(query.q ?? '').trim().toLowerCase();
return q ? `${path}?q=${encodeURIComponent(q)}` : path;
},
})
Handler-level keys can stay local to the route when only one endpoint needs custom behavior:
@CacheKey((context) => {
const tenant = context.requestContext.principal?.subject ?? 'anonymous';
const slug = String(context.requestContext.request.query.slug ?? 'index');
return `tenant:${tenant}:page:${slug}`;
})
The HTTP interceptor caches only successful, uncommitted GET handler results with a value that can be replayed later. It skips undefined, SseResponse streams, already committed responses, and responses whose status code is outside the 2xx range, so redirects and error responses are not stored as cache hits.
CacheService.reset() clears entries owned by the configured store, not unrelated application state. It also drops in-flight remember(...) bookkeeping so loaders that started before the reset cannot repopulate stale entries after the reset completes. For the built-in memory store that means the in-process entries held by that store instance. For Redis, ownership is the configured keyPrefix namespace; keep the default fluo:cache: or choose a dedicated prefix such as myapp:cache: for shared Redis deployments.
CacheModule.forRoot({
store: 'redis',
keyPrefix: 'myapp:cache:',
})
Avoid sharing a Redis cache prefix with non-cache data. del(key) removes the exact cache key resolved by this package, while reset() removes only the store-owned cache namespace described above.
When the application closes, CacheService forwards shutdown to custom stores that expose close() or dispose(). Use one of those optional hooks when a store owns sockets, pools, timers, or other external resources.
Custom stores can be passed directly through store when they implement the CacheStore contract. This is the right option for in-process LRU stores, remote caches other than Redis, or test doubles that need to observe cache operations.
Use CacheModule.forRoot(...) for normal application setup, including custom defineModule(...) composition.
import { defineModule } from '@fluojs/runtime';
import { CacheInterceptor, CacheModule, CacheService } from '@fluojs/cache-manager';
class ManualCacheModule {}
defineModule(ManualCacheModule, {
exports: [CacheService, CacheInterceptor],
imports: [CacheModule.forRoot({ store: 'memory', ttl: 60 })],
});
The built-in memory store is designed for single-process, bounded caching:
ttl on the default memory path, CacheModule.forRoot() uses a 300-second TTL.ttl: 0 is still supported for no-expiry entries, but the memory store keeps only the most recent 1,000 live keys.For non-GET handlers decorated with @CacheEvict(...), eviction is deferred until the response successfully commits. If response.send(...) rejects, the deferred eviction is cancelled so a failed commit does not drop the previous cached read result. If an adapter path never calls response.send(...), the interceptor still runs a bounded fallback timer so successful writes do not leave stale entries behind indefinitely. Deferred eviction failures stay contained inside the interceptor, so cache-key factories or cache-store deletes cannot surface as post-response unhandled promise rejections.
CacheModule.forRoot(options): Configures the cache store (memory/redis/custom), default TTL, key strategies, global, principalScopeResolver, the Redis namespace keyPrefix, and Redis options such as redis.scanCount.
This is the primary package entrypoint for application modules.CacheModuleOptions: Application-facing configuration accepted by CacheModule.forRoot(...).NormalizedCacheModuleOptions: Compatibility-only type export matching the normalized configuration shape after defaults are applied. Prefer CacheModuleOptions for application code; this type remains public so consumers that referenced the previously shipped declaration surface can keep compiling.CacheService: Main API for manual cache operations (get, set, del, remember, reset, close). Application shutdown calls the same close() path, which forwards teardown to custom stores exposing close() or dispose().@CacheTTL(seconds): Sets the TTL for a specific handler.@CacheKey(key): Sets a custom cache key or key factory for a specific handler.@CacheEvict(key): Clears one or more cache keys after a successful non-GET handler completes.cacheRouteMetadataKey, getCacheKeyMetadata(...), getCacheTtlMetadata(...), and getCacheEvictMetadata(...): Low-level metadata helpers exported for first-party interceptor integration, diagnostics, and advanced tooling that needs to inspect cache decorator metadata without reimplementing the metadata keys.CacheInterceptor: Handles automatic GET response caching and eviction logic.MemoryStore and RedisStore: Built-in store implementations.CACHE_OPTIONS and CACHE_STORE: DI tokens for package internals and custom composition.createCacheManagerPlatformStatusSnapshot(...) and createCacheManagerPlatformDiagnosticIssues(...): Platform status and diagnostic helpers.@fluojs/redis: Required for Redis storage.@fluojs/http: Required for HTTP interceptors and decorators.packages/cache-manager/src/module.test.ts: Module configuration and provider tests.packages/cache-manager/src/interceptor.test.ts: HTTP caching and eviction tests.packages/cache-manager/src/service.ts: Core CacheService implementation.packages/cache-manager/src/status.test.ts: Status and diagnostic helper tests.FAQs
Decorator-driven HTTP response caching and standalone cache service for Fluo, with memory and Redis backends.
We found that @fluojs/cache-manager demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
GitHub Actions checkout now blocks risky pull_request_target checkouts by default to help prevent pwn request supply chain attacks.

Product
Socket now supports Custom Roles and Repository Access Permissions so organizations can control who can access specific repositories and actions.

Product
Socket MCP now lets AI assistants review org alerts, investigate threats using the Socket threat feed, and inspect package files in addition to dependency scoring.