From e151c478392b78a51cdf5591ce270fd018e8b869 Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Fri, 17 Feb 2023 16:31:37 +0100 Subject: [PATCH 1/5] fix(common): fix cache ttl not beeing respected --- integration/cache/e2e/custom-ttl.spec.ts | 35 ++++++++++++++++ .../src/custom-ttl/custom-ttl.controller.ts | 20 ++++++++++ .../cache/src/custom-ttl/custom-ttl.module.ts | 8 ++++ .../cache/src/custom-ttl/tsconfig.json | 40 +++++++++++++++++++ .../cache/interceptors/cache.interceptor.ts | 16 +++++++- 5 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 integration/cache/e2e/custom-ttl.spec.ts create mode 100644 integration/cache/src/custom-ttl/custom-ttl.controller.ts create mode 100644 integration/cache/src/custom-ttl/custom-ttl.module.ts create mode 100644 integration/cache/src/custom-ttl/tsconfig.json 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..52f6fbdbe4d 100644 --- a/packages/common/cache/interceptors/cache.interceptor.ts +++ b/packages/common/cache/interceptors/cache.interceptor.ts @@ -1,3 +1,4 @@ +import { Store } from 'cache-manager'; import { Observable, of } from 'rxjs'; import { tap } from 'rxjs/operators'; import { Inject, Injectable, Optional } from '../../decorators'; @@ -9,7 +10,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, @@ -19,6 +21,13 @@ import { const HTTP_ADAPTER_HOST = 'HttpAdapterHost'; const REFLECTOR = 'Reflector'; +// We need to check if the cache-manager package is v5 or greater +// because the set method signature changed in v5 +const cacheManager = loadPackage('cache-manager', 'CacheModule', () => + require('cache-manager'), +); +const cacheManagerIsv5OrGreater = 'memoryStore' in cacheManager; + export interface HttpAdapterHost { httpAdapter: T; } @@ -65,7 +74,10 @@ export class CacheInterceptor implements NestInterceptor { return; } - const args = isNil(ttl) ? [key, response] : [key, response, { ttl }]; + const args = [key, response]; + if (!isNil(ttl)) { + args.push(cacheManagerIsv5OrGreater ? ttl : { ttl }); + } try { await this.cacheManager.set(...args); From 63814af6e63b8c4eebeb65ca7baec0b421864e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kr=C3=BCger?= Date: Sun, 19 Feb 2023 18:24:17 +0100 Subject: [PATCH 2/5] Update packages/common/cache/interceptors/cache.interceptor.ts Co-authored-by: Micael Levi L. Cavalcante --- packages/common/cache/interceptors/cache.interceptor.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/common/cache/interceptors/cache.interceptor.ts b/packages/common/cache/interceptors/cache.interceptor.ts index 52f6fbdbe4d..5043772ca2c 100644 --- a/packages/common/cache/interceptors/cache.interceptor.ts +++ b/packages/common/cache/interceptors/cache.interceptor.ts @@ -1,4 +1,3 @@ -import { Store } from 'cache-manager'; import { Observable, of } from 'rxjs'; import { tap } from 'rxjs/operators'; import { Inject, Injectable, Optional } from '../../decorators'; From e7b38a0dcdfac69b4b5c7c3bc6119c7254452857 Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Sun, 19 Feb 2023 21:18:45 +0100 Subject: [PATCH 3/5] refactor(common): refactor cache-manager version detection refactor cache-manager version detection in CacheInterceptor to be lazy --- .../cache/interceptors/cache.interceptor.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/common/cache/interceptors/cache.interceptor.ts b/packages/common/cache/interceptors/cache.interceptor.ts index 5043772ca2c..81c82ea15d7 100644 --- a/packages/common/cache/interceptors/cache.interceptor.ts +++ b/packages/common/cache/interceptors/cache.interceptor.ts @@ -20,13 +20,6 @@ import { const HTTP_ADAPTER_HOST = 'HttpAdapterHost'; const REFLECTOR = 'Reflector'; -// We need to check if the cache-manager package is v5 or greater -// because the set method signature changed in v5 -const cacheManager = loadPackage('cache-manager', 'CacheModule', () => - require('cache-manager'), -); -const cacheManagerIsv5OrGreater = 'memoryStore' in cacheManager; - export interface HttpAdapterHost { httpAdapter: T; } @@ -67,6 +60,14 @@ export class CacheInterceptor implements NestInterceptor { const ttl = isFunction(ttlValueOrFactory) ? await ttlValueOrFactory(context) : ttlValueOrFactory; + + // We need to check if the cache-manager package is v5 or greater + // because the set method signature changed in v5 + const cacheManager = loadPackage('cache-manager', 'CacheModule', () => + require('cache-manager'), + ); + const cacheManagerIsv5OrGreater = 'memoryStore' in cacheManager; + return next.handle().pipe( tap(async response => { if (response instanceof StreamableFile) { From 7ef71f3f9be6e0199915f6fa13f214eca1c1791e Mon Sep 17 00:00:00 2001 From: Jan Krueger Date: Sun, 19 Feb 2023 21:48:33 +0100 Subject: [PATCH 4/5] fix(common): fix cache-manager version detection running every request move cache-manager version detection to constructor --- .../cache/interceptors/cache.interceptor.ts | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/common/cache/interceptors/cache.interceptor.ts b/packages/common/cache/interceptors/cache.interceptor.ts index 81c82ea15d7..1f2f5fb5498 100644 --- a/packages/common/cache/interceptors/cache.interceptor.ts +++ b/packages/common/cache/interceptors/cache.interceptor.ts @@ -36,10 +36,21 @@ 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, @@ -61,13 +72,6 @@ export class CacheInterceptor implements NestInterceptor { ? await ttlValueOrFactory(context) : ttlValueOrFactory; - // We need to check if the cache-manager package is v5 or greater - // because the set method signature changed in v5 - const cacheManager = loadPackage('cache-manager', 'CacheModule', () => - require('cache-manager'), - ); - const cacheManagerIsv5OrGreater = 'memoryStore' in cacheManager; - return next.handle().pipe( tap(async response => { if (response instanceof StreamableFile) { @@ -76,7 +80,7 @@ export class CacheInterceptor implements NestInterceptor { const args = [key, response]; if (!isNil(ttl)) { - args.push(cacheManagerIsv5OrGreater ? ttl : { ttl }); + args.push(this.cacheManagerIsv5OrGreater ? ttl : { ttl }); } try { From ee11bea4edb9a33454222756fa88a9cda1f038dd Mon Sep 17 00:00:00 2001 From: Kamil Mysliwiec Date: Wed, 15 Mar 2023 08:17:02 +0100 Subject: [PATCH 5/5] Update packages/common/cache/interceptors/cache.interceptor.ts --- packages/common/cache/interceptors/cache.interceptor.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/common/cache/interceptors/cache.interceptor.ts b/packages/common/cache/interceptors/cache.interceptor.ts index 1f2f5fb5498..5933239703f 100644 --- a/packages/common/cache/interceptors/cache.interceptor.ts +++ b/packages/common/cache/interceptors/cache.interceptor.ts @@ -38,6 +38,7 @@ export class CacheInterceptor implements NestInterceptor { protected allowedMethods = ['GET']; private cacheManagerIsv5OrGreater: boolean; + constructor( @Inject(CACHE_MANAGER) protected readonly cacheManager: any, @Inject(REFLECTOR) protected readonly reflector: any,