@nhan2804/nestjs-cacheable
Version:
@nestjs/cacheable is a flexible caching library for NestJS that leverages Redis for efficient and scalable storage. With support for function-level caching through easy-to-use decorators, it allows developers to seamlessly enhance performance while mainta
149 lines (133 loc) • 4.73 kB
text/typescript
import { Inject } from "@nestjs/common";
import { Cache } from "cache-manager";
import { CACHE_MANAGER } from "@nestjs/cache-manager";
import { cacheConfigOption, getCacheManager } from "./cacheable.helpers";
import { CacheableOptions } from "./cacheable.interfaces";
type CacheKeyFn = (args: any[]) => string;
interface CacheOptions {
ttl?: number; // Thời gian sống của cache (TTL)
refreshThreshold?: number; // Tỷ lệ để tự động làm mới cache (default 0.9)
key?: string | CacheKeyFn; // Key có thể là chuỗi hoặc hàm tạo key
prefix?: string; // Tiền tố để xác định key cần xóa
type?: "bust" | "delete"; // Các hành động như bust (làm mới cache) hoặc delete (xóa cache)
concatArgs?: boolean; // Chỉ định có kết hợp các tham số hàm vào key không
}
const log = (m: any) => {
if (cacheConfigOption.isDev) {
console.log(m);
}
};
export function Cacheable(options?: CacheOptions): MethodDecorator {
return (
target: any,
propertyKey: string | symbol,
descriptor: PropertyDescriptor
) => {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: any[]) {
const {
ttl,
refreshThreshold,
key,
prefix,
type,
concatArgs = true,
} = {
...cacheConfigOption,
...options,
};
const cacheManager: Cache = getCacheManager();
const initKey = `${
target.constructor.name
}-${propertyKey.toString()}-${args
.map((arg) => (typeof arg === "object" ? JSON.stringify(arg) : arg))
.join("-")}`;
let cacheKey: string = initKey;
log({
ttl,
refreshThreshold,
key,
prefix,
type,
concatArgs,
});
if (typeof key === "string") {
cacheKey = key;
if (concatArgs) {
cacheKey = `${key}-${initKey}`;
}
} else if (typeof key === "function") {
cacheKey = key(args);
}
if (!cacheKey) {
throw new Error("cacheKey can not be empty!");
}
log("key " + cacheKey);
const currentTime = Date.now();
// Nếu `type='delete'`, xóa cache
if (type === "delete") {
log(`Deleting cache for key: ${cacheKey}`);
await cacheManager.del(cacheKey); // Xóa key hiện tại
// Nếu có prefix, xóa tất cả các keys có prefix
if (prefix) {
const allKeys = await cacheManager.store.keys(); // Lấy tất cả các keys trong cache
const prefixKeys = allKeys.filter((key) => key.startsWith(prefix));
for (const key of prefixKeys) {
await cacheManager.del(key); // Xóa các keys có prefix
log(`Deleted cache for key with prefix: ${key}`);
}
}
// Gọi lại hàm gốc để tạo cache mới
const newValue = await originalMethod.apply(this, args);
await cacheManager.set(
cacheKey,
{ value: newValue, timestamp: currentTime },
ttl
);
return newValue;
}
// Nếu `type='bust'`, làm mới cache
if (type === "bust") {
log(`Force refresh cache for key: ${cacheKey}`);
const newValue = await originalMethod.apply(this, args);
await cacheManager.set(
cacheKey,
{ value: newValue, timestamp: currentTime },
ttl
);
return newValue;
}
// Kiểm tra cache
const cachedData = await cacheManager.get<{
value: any;
timestamp: number;
}>(cacheKey);
if (cachedData) {
const { value, timestamp } = cachedData;
const elapsed = (currentTime - timestamp) / 1000; // Thời gian đã trôi qua (giây)
const throttleTime = ttl * (refreshThreshold || elapsed);
if (elapsed >= throttleTime) {
log(`Auto-refresh cache for key: ${cacheKey}`);
const newValue = await originalMethod.apply(this, args);
await cacheManager.set(
cacheKey,
{ value: newValue, timestamp: currentTime },
ttl
);
return newValue;
}
log(`Cache hit for key: ${cacheKey}`);
return value;
}
log(`Cache miss for key: ${cacheKey}`);
const result = await originalMethod.apply(this, args);
await cacheManager.set(
cacheKey,
{ value: result, timestamp: currentTime },
ttl
);
return result;
};
return descriptor;
};
}