diff --git a/integration/cache/e2e/custom-ttl.spec.ts b/integration/cache/e2e/custom-ttl.spec.ts new file mode 100644 index 00000000000..9fcd9f62038 --- /dev/null +++ b/integration/cache/e2e/custom-ttl.spec.ts @@ -0,0 +1,35 @@ +import { INestApplication } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import * as request from 'supertest'; +import { CustomTtlModule } from '../src/custom-ttl/custom-ttl.module'; + +describe('Caching Custom TTL', () => { + let server; + let app: INestApplication; + + beforeEach(async () => { + const module = await Test.createTestingModule({ + imports: [CustomTtlModule], + }).compile(); + + app = module.createNestApplication(); + server = app.getHttpServer(); + await app.init(); + }); + + it('should return a differnt value after the TTL is elapsed', async () => { + await request(server).get('/').expect(200, '0'); + await new Promise(resolve => setTimeout(resolve, 500)); + await request(server).get('/').expect(200, '1'); + }); + + it('should return the cached value within the TTL', async () => { + await request(server).get('/').expect(200, '0'); + await new Promise(resolve => setTimeout(resolve, 200)); + await request(server).get('/').expect(200, '0'); + }); + + afterEach(async () => { + await app.close(); + }); +}); diff --git a/integration/cache/src/custom-ttl/custom-ttl.controller.ts b/integration/cache/src/custom-ttl/custom-ttl.controller.ts new file mode 100644 index 00000000000..2c4caac1726 --- /dev/null +++ b/integration/cache/src/custom-ttl/custom-ttl.controller.ts @@ -0,0 +1,20 @@ +import { + CacheInterceptor, + CacheTTL, + Controller, + Get, + UseInterceptors, +} from '@nestjs/common'; + +@Controller() +export class CustomTtlController { + counter = 0; + constructor() {} + + @Get() + @CacheTTL(500) + @UseInterceptors(CacheInterceptor) + getNumber() { + return this.counter++; + } +} diff --git a/integration/cache/src/custom-ttl/custom-ttl.module.ts b/integration/cache/src/custom-ttl/custom-ttl.module.ts new file mode 100644 index 00000000000..d44b46e018e --- /dev/null +++ b/integration/cache/src/custom-ttl/custom-ttl.module.ts @@ -0,0 +1,8 @@ +import { CacheModule, Module } from '@nestjs/common'; +import { CustomTtlController } from './custom-ttl.controller'; + +@Module({ + imports: [CacheModule.register()], + controllers: [CustomTtlController], +}) +export class CustomTtlModule {} diff --git a/integration/cache/src/custom-ttl/tsconfig.json b/integration/cache/src/custom-ttl/tsconfig.json new file mode 100644 index 00000000000..904941e7088 --- /dev/null +++ b/integration/cache/src/custom-ttl/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "module": "commonjs", + "declaration": false, + "noImplicitAny": false, + "removeComments": true, + "noLib": false, + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "target": "es6", + "sourceMap": true, + "allowJs": true, + "outDir": "./dist", + "paths": { + "@nestjs/common": ["../../packages/common"], + "@nestjs/common/*": ["../../packages/common/*"], + "@nestjs/core": ["../../packages/core"], + "@nestjs/core/*": ["../../packages/core/*"], + "@nestjs/microservices": ["../../packages/microservices"], + "@nestjs/microservices/*": ["../../packages/microservices/*"], + "@nestjs/websockets": ["../../packages/websockets"], + "@nestjs/websockets/*": ["../../packages/websockets/*"], + "@nestjs/testing": ["../../packages/testing"], + "@nestjs/testing/*": ["../../packages/testing/*"], + "@nestjs/platform-express": ["../../packages/platform-express"], + "@nestjs/platform-express/*": ["../../packages/platform-express/*"], + "@nestjs/platform-socket.io": ["../../packages/platform-socket.io"], + "@nestjs/platform-socket.io/*": ["../../packages/platform-socket.io/*"], + "@nestjs/platform-ws": ["../../packages/platform-ws"], + "@nestjs/platform-ws/*": ["../../packages/platform-ws/*"] + } + }, + "include": [ + "src/**/*", + "e2e/**/*" + ], + "exclude": [ + "node_modules", + ] +} diff --git a/packages/common/cache/interceptors/cache.interceptor.ts b/packages/common/cache/interceptors/cache.interceptor.ts index 9e91d4bed7b..5933239703f 100644 --- a/packages/common/cache/interceptors/cache.interceptor.ts +++ b/packages/common/cache/interceptors/cache.interceptor.ts @@ -9,7 +9,8 @@ import { NestInterceptor, } from '../../interfaces'; import { Logger } from '../../services/logger.service'; -import { isFunction, isNil } from '../../utils/shared.utils'; +import { loadPackage } from '../../utils/load-package.util'; +import { isFunction, isNil, isNumber } from '../../utils/shared.utils'; import { CACHE_KEY_METADATA, CACHE_MANAGER, @@ -35,10 +36,22 @@ export class CacheInterceptor implements NestInterceptor { protected readonly httpAdapterHost: HttpAdapterHost; protected allowedMethods = ['GET']; + + private cacheManagerIsv5OrGreater: boolean; + constructor( @Inject(CACHE_MANAGER) protected readonly cacheManager: any, @Inject(REFLECTOR) protected readonly reflector: any, - ) {} + ) { + // We need to check if the cache-manager package is v5 or greater + // because the set method signature changed in v5 + const cacheManagerPackage = loadPackage( + 'cache-manager', + 'CacheModule', + () => require('cache-manager'), + ); + this.cacheManagerIsv5OrGreater = 'memoryStore' in cacheManagerPackage; + } async intercept( context: ExecutionContext, @@ -59,13 +72,17 @@ export class CacheInterceptor implements NestInterceptor { const ttl = isFunction(ttlValueOrFactory) ? await ttlValueOrFactory(context) : ttlValueOrFactory; + return next.handle().pipe( tap(async response => { if (response instanceof StreamableFile) { return; } - const args = isNil(ttl) ? [key, response] : [key, response, { ttl }]; + const args = [key, response]; + if (!isNil(ttl)) { + args.push(this.cacheManagerIsv5OrGreater ? ttl : { ttl }); + } try { await this.cacheManager.set(...args);