Skip to content

Commit 643e84c

Browse files
feature(common) cache module
1 parent 25c7e89 commit 643e84c

13 files changed

Lines changed: 206 additions & 0 deletions
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const CACHE_MANAGER = 'CACHE_MANAGER';
2+
export const CACHE_MODULE_OPTIONS = 'CACHE_MODULE_OPTIONS';
3+
export const CACHE_KEY_METADATA = 'cache_module:cache_key';
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Module } from '../decorators';
2+
import { DynamicModule, Provider } from '../interfaces';
3+
import { CACHE_MANAGER, CACHE_MODULE_OPTIONS } from './cache.constants';
4+
import { createCacheManager } from './cache.providers';
5+
import {
6+
CacheModuleAsyncOptions,
7+
CacheModuleOptions,
8+
CacheOptionsFactory,
9+
} from './interfaces/cache-module.interface';
10+
11+
@Module({
12+
providers: [createCacheManager()],
13+
exports: [CACHE_MANAGER],
14+
})
15+
export class CacheModule {
16+
static register(options: CacheModuleOptions = {}): DynamicModule {
17+
return {
18+
module: CacheModule,
19+
providers: [{ provide: CACHE_MODULE_OPTIONS, useValue: options }],
20+
};
21+
}
22+
23+
static registerAsync(options: CacheModuleAsyncOptions): DynamicModule {
24+
return {
25+
module: CacheModule,
26+
imports: options.imports,
27+
providers: this.createAsyncProviders(options),
28+
};
29+
}
30+
31+
private static createAsyncProviders(
32+
options: CacheModuleAsyncOptions,
33+
): Provider[] {
34+
if (options.useExisting || options.useFactory) {
35+
return [this.createAsyncOptionsProvider(options)];
36+
}
37+
return [
38+
this.createAsyncOptionsProvider(options),
39+
{
40+
provide: options.useClass,
41+
useClass: options.useClass,
42+
},
43+
];
44+
}
45+
46+
private static createAsyncOptionsProvider(
47+
options: CacheModuleAsyncOptions,
48+
): Provider {
49+
if (options.useFactory) {
50+
return {
51+
provide: CACHE_MODULE_OPTIONS,
52+
useFactory: options.useFactory,
53+
inject: options.inject || [],
54+
};
55+
}
56+
return {
57+
provide: CACHE_MODULE_OPTIONS,
58+
useFactory: async (optionsFactory: CacheOptionsFactory) =>
59+
await optionsFactory.createCacheOptions(),
60+
inject: [options.useExisting || options.useClass],
61+
};
62+
}
63+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { Provider } from '../interfaces';
2+
import { loadPackage } from '../utils/load-package.util';
3+
import { CACHE_MANAGER, CACHE_MODULE_OPTIONS } from './cache.constants';
4+
import { defaultCacheOptions } from './default-options';
5+
import { CacheManagerOptions } from './interfaces/cache-manager.interface';
6+
7+
export function createCacheManager(): Provider {
8+
return {
9+
provide: CACHE_MANAGER,
10+
useFactory: (options: CacheManagerOptions) => {
11+
const cacheManager = loadPackage('cache-manager', 'CacheModule');
12+
const memoryCache = cacheManager.caching({
13+
...defaultCacheOptions,
14+
...((options || {}) as any),
15+
});
16+
return memoryCache;
17+
},
18+
inject: [CACHE_MODULE_OPTIONS],
19+
};
20+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { ReflectMetadata } from '../../decorators';
2+
import { CACHE_KEY_METADATA } from '../cache.constants';
3+
4+
export const CacheKey = (key: string) =>
5+
ReflectMetadata(CACHE_KEY_METADATA, key);
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './cache-key.decorator';
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const defaultCacheOptions = {
2+
ttl: 5,
3+
max: 100,
4+
store: 'memory',
5+
};

packages/common/cache/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
export * from './cache.module';
2+
export * from './decorators';
3+
export * from './interceptors';
4+
export * from './interfaces';
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { Observable, of } from 'rxjs';
2+
import { tap } from 'rxjs/operators';
3+
import { Inject, Injectable, Optional } from '../../decorators';
4+
import {
5+
ExecutionContext,
6+
HttpServer,
7+
NestInterceptor,
8+
} from '../../interfaces';
9+
import { CACHE_KEY_METADATA, CACHE_MANAGER } from '../cache.constants';
10+
11+
// NOTE (external)
12+
// We need to deduplicate them here due to the circular dependency
13+
// between core and common packages
14+
const HTTP_SERVER_REF = 'HTTP_SERVER_REF';
15+
const REFLECTOR = 'Reflector';
16+
17+
@Injectable()
18+
export class CacheInterceptor implements NestInterceptor {
19+
private readonly isHttpApp: boolean;
20+
21+
constructor(
22+
@Optional()
23+
@Inject(HTTP_SERVER_REF)
24+
private readonly httpServer: HttpServer,
25+
@Inject(CACHE_MANAGER) private readonly cacheManager: any,
26+
@Inject(REFLECTOR) private readonly reflector,
27+
) {
28+
this.isHttpApp = httpServer && !!httpServer.getRequestMethod;
29+
}
30+
31+
async intercept(
32+
context: ExecutionContext,
33+
call$: Observable<any>,
34+
): Promise<Observable<any>> {
35+
const key = this.getCacheKey(context);
36+
if (!key) {
37+
return call$;
38+
}
39+
try {
40+
const value = await this.cacheManager.get(key);
41+
if (value) {
42+
return of(value);
43+
}
44+
return call$.pipe(tap(response => this.cacheManager.set(key, response)));
45+
} catch {
46+
return call$;
47+
}
48+
}
49+
50+
getCacheKey(context: ExecutionContext): string | undefined {
51+
if (!this.isHttpApp) {
52+
return this.reflector.get(CACHE_KEY_METADATA, context.getHandler());
53+
}
54+
const request = context.getArgByIndex(0);
55+
if (this.httpServer.getRequestMethod(request) !== 'GET') {
56+
return undefined;
57+
}
58+
return this.httpServer.getRequestUrl(context.getArgByIndex(0));
59+
}
60+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './cache.interceptor';
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export interface LiteralObject {
2+
[key: string]: any;
3+
}
4+
5+
export interface CacheStore {
6+
set<T>(key: string, value: T): Promise<void> | void;
7+
get<T>(key: string): Promise<void> | void;
8+
del(key: string): void | Promise<void>;
9+
}
10+
11+
export interface CacheStoreFactory {
12+
create(args: LiteralObject): CacheStore;
13+
}
14+
15+
export interface CacheManagerOptions {
16+
store?: string | CacheStoreFactory;
17+
ttl?: number;
18+
max?: number;
19+
isCacheableValue?: (value: any) => boolean;
20+
}

0 commit comments

Comments
 (0)