UNPKG

@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
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; }; }