cache-manager
cache-manager copied to clipboard
SWC builder causes cacheFactory is always create a new instance of KeyV
Is there an existing issue for this?
- [x] I have searched the existing issues
Current behavior
Using swc as the compiler causes @nestjs/cache-manager's cache.provider.js' cachingFactory to always use the else case
function createCacheManager() {
return {
provide: cache_constants_1.CACHE_MANAGER,
useFactory: async (options) => {
const cachingFactory = async (store, options) => {
if (store instanceof keyv_1.default) { // <--------- this line is always false even if i am passing in an instance of KeyV as the store
return store;
}
return new keyv_1.default({
store,
ttl: options.ttl,
namespace: options.namespace,
});
};
...
I am not sure why this is the case 🤔
What is unfortunate about this behaviour is I cannot follow the examples in the nestjs documentation. I have to find out the hard way on how to get @nestjs/cache-manager to use @keyv/redis properly. What ends up happening is, even if your store is an instance of KeyV, it'll get wrapped by another instance KeyV and the options wont be passed in properly and wont use @keyv/redis as the underlying cache store
This is the nestjs example i am talking about:
import { Module } from '@nestjs/common';
import { CacheModule } from '@nestjs/cache-manager';
import { AppController } from './app.controller';
import { createKeyv } from '@keyv/redis';
import { Keyv } from 'keyv';
import { CacheableMemory } from 'cacheable';
@Module({
imports: [
CacheModule.registerAsync({
useFactory: async () => {
return {
stores: [
new Keyv({
store: new CacheableMemory({ ttl: 60000, lruSize: 5000 }),
}),
createKeyv('redis://localhost:6379'),
],
};
},
}),
],
controllers: [AppController],
})
export class AppModule {}
Minimum reproduction code
https://github.com/yawhide/nestjs-cache-manager
Steps to reproduce
clone my repo npm install
store !== keyv_1.default
npm run start:debug
put a breakpoint on line 21 in node_modules/@nestjs/cache-manager/dist/cache.providers.js (if (store instanceof keyv_1.default) {)
observe that this line will always be false.
store === keyv_1.default
delete "builder": "swc" in nest-cli.json
npm run start:debug
put a breakpoint on line 21 in node_modules/@nestjs/cache-manager/dist/cache.providers.js (if (store instanceof keyv_1.default) {)
observe that this line will be true
Expected behavior
i can use swc as the builder and when I pass in an instance of Keyv, @nestjs/manager wont wrap it in another instance of KeyV
Package version
3.0.1
NestJS version
11.1.0
Node.js version
20.19.0
In which operating systems have you tested?
- [x] macOS
- [ ] Windows
- [ ] Linux
Other
No response
I am not sure why this is the case 🤔
that may be related with having multiple keyv packages loaded in your app. Check that out! With NPM it would be:
npm ls keyv
i thought so too! but unfortunately, i dont think that is the case because when i was in the debugger stepping through @nestjs/cache-manager and keyv, i would check what version each package is and it was consistent: 5.3.3 for keyv and 3.0.1 for nestjs/cache-manager.
if i build with swc, store !== keyv_1.default. If i build with typescript, store === keyv_1.default
Could it be something to do with commonjs vs esm?
As this is not supposed to be a package manager related issue, can you please update the reproduction to use NPM instead? then make sure that the commands are working as you said in the steps to reproduce
As this is not supposed to be a package manager related issue, can you please update the reproduction to use NPM instead? then make sure that the commands are working as you said in the steps to reproduce
I updated the steps to reproduce to use npm instead of yarn
I'm getting this SWC error on npm run start:dev
[Nest] 236146 - 05/09/2025, 4:10:11 PM ERROR [ExceptionHandler] TypeError: Cannot read properties of undefined (reading 'includes')
at /tmp/nestjs-cache-manager/node_modules/keyv/dist/index.cjs:437:123
at Array.some (<anonymous>)
at Keyv._checkIterableAdapter (/tmp/nestjs-cache-manager/node_modules/keyv/dist/index.cjs:437:84)
at new Keyv (/tmp/nestjs-cache-manager/node_modules/keyv/dist/index.cjs:283:72)
at cachingFactory (/tmp/nestjs-cache-manager/node_modules/@nestjs/cache-manager/dist/cache.providers.js:24:24)
at /tmp/nestjs-cache-manager/node_modules/@nestjs/cache-manager/dist/cache.providers.js:31:65
at Array.map (<anonymous>)
at InstanceWrapper.useFactory [as metatype] (/tmp/nestjs-cache-manager/node_modules/@nestjs/cache-manager/dist/cache.providers.js:31:52)
at Injector.instantiateClass (/tmp/nestjs-cache-manager/node_modules/@nestjs/core/injector/injector.js:376:55)
at callback (/tmp/nestjs-cache-manager/node_modules/@nestjs/core/injector/injector.js:65:45)
same as https://github.com/jaredwray/keyv/issues/1300 I guess
so changing your code to:
stores: [new KeyvRedis({
socket: {
host: "localhost",
port: 6379,
},
})],
addresses that
I've managed to reproduce that using the code snippet from the nestjs docs. It only breaks if we use SWC builder but I'm not sure if this is something that we can address on nestjs side to be honest
same as jaredwray/keyv#1300 I guess
so changing your code to:
stores: [new KeyvRedis({ socket: { host: "localhost", port: 6379, }, })],addresses that
Yeah, that has been my workaround as well.
I've managed to reproduce that using the code snippet from the nestjs docs. It only breaks if we use SWC builder but I'm not sure if this is something that we can address on nestjs side to be honest
if you think a fix is not possible, then maybe it is worth documenting the alternative way of using @nestjs/cache-manager where you pass in a KeyvRedis instance instead?
any updates on this? Our caches seem to not be connecting to redis and silently defaulting to in mem cache.
@scoobaSteve007
@Module({
imports: [
CacheModule.registerAsync({
useFactory: async () => {
return {
stores: [
new Keyv({
store: new CacheableMemory({ ttl: 60000, lruSize: 5000 }),
}),
createKeyv('redis://localhost:6379'),
],
};
},
}),
],
controllers: [AppController],
})
this code use two cache store in-memory cache is default and redis is fallback
Documentation said...
In this example, we've registered two stores: CacheableMemory and KeyvRedis. The CacheableMemory store is a simple in-memory store, while KeyvRedis is a Redis store. The stores array is used to specify the stores you want to use. The first store in the array is the default store, and the rest are fallback stores.
@yawhide I think, it is connected to ESM / CJS.
I'm encountering microsoft/typescript#62328 using @nestjs/cache-manager and @keyv/redis together in a way outlined in the docs. The reason for it is that keyv exports both ESM and CJS definitions, and while @keyv/redis uses ESM ones, @nestjs/cache-manager uses CJS ones, and as @andarist user pointed out, instances of those classes are not completely interchangeable (different module systems, different prototype chains).
The reason new KeyvRedis(…) works is because stores property accepts either Keyv instance (and Keyv is a class) or KeyvStoreAdapter-shaped object (and KeyvStoreAdapter is a type, not a class). Since class KeyvRedis implements KeyvStoreAdapter, even though KeyvStoreAdapter is defined twice (once per each module system), the type information is the same, and it doesn't exist at runtime, so there is no error here.
Proofs:
- this breaks: https://tsplay.dev/m0jyrw
- this works: https://tsplay.dev/wEKvbm
I think, the most durable way of fixing both issues (the OP's and mine) would be for @nestjs/cache-manager to start providing both CJS and ESM definitions. In the meantime, it's easy to just update the docs from createKeyv(…) to new KeyvRedis(…).