UNPKG

koatty_cacheable

Version:
385 lines (380 loc) 13.7 kB
'use strict'; var koatty_lib = require('koatty_lib'); var koatty_logger = require('koatty_logger'); var koatty_store = require('koatty_store'); var koatty_container = require('koatty_container'); /*! * @Author: richen * @Date: 2026-04-24 08:20:32 * @License: BSD (3-Clause) * @Copyright (c) - <richenlin(at)gmail.com> * @HomePage: https://koatty.org/ */ var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var storeCache = { store: null }; var initPromise = null; async function GetCacheStore(options) { if (storeCache.store && storeCache.store.getConnection) { return storeCache.store; } if (initPromise) { return initPromise; } if (koatty_lib.Helper.isEmpty(options)) { if (!storeCache.store) { koatty_logger.DefaultLogger.Warn(`CacheStore not initialized. Please call KoattyCached() first with proper options in your application startup.`); } return storeCache.store || null; } initPromise = (async () => { try { storeCache.store = koatty_store.CacheStore.getInstance(options); if (!koatty_lib.Helper.isFunction(storeCache.store.getConnection)) { throw Error(`CacheStore connection failed. `); } await storeCache.store.client.getConnection(); return storeCache.store; } finally { initPromise = null; } })(); return initPromise; } __name(GetCacheStore, "GetCacheStore"); async function CloseCacheStore() { if (storeCache.store) { try { if (storeCache.store.client) { const client = storeCache.store.client; if (typeof client.quit === "function") { await client.quit(); } else if (typeof client.close === "function") { await client.close(); } } } catch { } } try { await koatty_store.CacheStore.clearAllInstances(); } catch { } storeCache.store = null; initPromise = null; } __name(CloseCacheStore, "CloseCacheStore"); var longKey = 128; function getArgs(func) { try { const funcStr = func.toString().replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").replace(/\s+/g, " "); const argsMatch = funcStr.match(/(?:async\s+)?(?:function\s*)?(?:\w+\s*)?\(([^)]*)\)/) || funcStr.match(/(?:async\s+)?\(([^)]*)\)/) || funcStr.match(/^(?:async\s+)?([^(]+)=>/); if (!argsMatch) { return []; } const argsString = argsMatch[1] || argsMatch[0] || ""; if (!argsString.trim()) { return []; } return argsString.split(",").map(function(a) { const trimmed = a.trim(); const nameMatch = trimmed.match(/^(\w+)/); return nameMatch ? nameMatch[1] : ""; }).filter(function(name) { return name && name !== "_"; }); } catch (error) { return []; } } __name(getArgs, "getArgs"); function getParamIndex(funcParams, params) { return params.map((param) => funcParams.indexOf(param)); } __name(getParamIndex, "getParamIndex"); function generateCacheKey(cacheName, paramIndexes, paramNames, props) { let key = cacheName; const hasUnresolved = paramIndexes.some((idx) => idx < 0); if (hasUnresolved) { key += `:${JSON.stringify(props)}`; } else { for (let i = 0; i < paramIndexes.length; i++) { const paramIndex = paramIndexes[i]; if (paramIndex >= 0 && props[paramIndex] !== void 0) { key += `:${paramNames[i]}:${koatty_lib.Helper.toString(props[paramIndex])}`; } } } return key.length > longKey ? koatty_lib.Helper.murmurHash(key) : key; } __name(generateCacheKey, "generateCacheKey"); function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } __name(delay, "delay"); async function asyncDelayedExecution(fn, ms) { await delay(ms); return fn(); } __name(asyncDelayedExecution, "asyncDelayedExecution"); // src/cache.ts function CacheAble(cacheName, opt = { params: [], timeout: 300 }) { return koatty_container.IOCContainer.createDecorator(({ target, methodName, descriptor, method, context }) => { const mergedOpt = { ...{ params: [], timeout: 300 }, ...opt }; if (context) { context.addInitializer?.(function() { const componentType = koatty_container.IOCContainer.getType(this.constructor); if (![ "SERVICE", "COMPONENT" ].includes(componentType)) { throw Error("This decorator only used in the service\u3001component class."); } }); const originalMethod = method; const funcParams = getArgs(originalMethod); const paramIndexes = getParamIndex(funcParams, mergedOpt.params || []); const invalidParams = []; (mergedOpt.params || []).forEach((param, index) => { if (paramIndexes[index] === -1) { invalidParams.push(param); } }); if (invalidParams.length > 0) { koatty_logger.DefaultLogger.Warn(`CacheAble: Parameter(s) [${invalidParams.join(", ")}] not found in method ${String(methodName)}. These parameters will be ignored.`); } return async function(...props) { const store = await GetCacheStore().catch((e) => { koatty_logger.DefaultLogger.error("Get cache store instance failed." + e.message); return null; }); if (store) { const key = generateCacheKey(cacheName, paramIndexes, mergedOpt.params, props); const res = await store.get(key).catch((e) => { koatty_logger.DefaultLogger.error("Cache get error:" + e.message); }); if (!koatty_lib.Helper.isEmpty(res)) { try { return JSON.parse(res); } catch (e) { const error = e; koatty_logger.DefaultLogger.error("Cache JSON parse error:" + error.message); store.del(key).catch((err) => { koatty_logger.DefaultLogger.error("Cache del error after parse failure:" + err.message); }); } } const result = await originalMethod.apply(this, props); store.set(key, koatty_lib.Helper.isJSONObj(result) ? JSON.stringify(result) : result, mergedOpt.timeout).catch((e) => { koatty_logger.DefaultLogger.error("Cache set error:" + e.message); }); return result; } else { return originalMethod.apply(this, props); } }; } else { const componentType = koatty_container.IOCContainer.getType(target); if (![ "SERVICE", "COMPONENT" ].includes(componentType)) { throw Error("This decorator only used in the service\u3001component class."); } const { value, configurable, enumerable } = descriptor; const funcParams = getArgs(target[methodName]); const paramIndexes = getParamIndex(funcParams, mergedOpt.params || []); const invalidParams = []; (mergedOpt.params || []).forEach((param, index) => { if (paramIndexes[index] === -1) { invalidParams.push(param); } }); if (invalidParams.length > 0) { koatty_logger.DefaultLogger.Warn(`CacheAble: Parameter(s) [${invalidParams.join(", ")}] not found in method ${String(methodName)}. These parameters will be ignored.`); } return { configurable, enumerable, writable: true, async value(...props) { const store = await GetCacheStore().catch((e) => { koatty_logger.DefaultLogger.error("Get cache store instance failed." + e.message); return null; }); if (store) { const key = generateCacheKey(cacheName, paramIndexes, mergedOpt.params, props); const res = await store.get(key).catch((e) => { koatty_logger.DefaultLogger.error("Cache get error:" + e.message); }); if (!koatty_lib.Helper.isEmpty(res)) { try { return JSON.parse(res); } catch (e) { const error = e; koatty_logger.DefaultLogger.error("Cache JSON parse error:" + error.message); store.del(key).catch((err) => { koatty_logger.DefaultLogger.error("Cache del error after parse failure:" + err.message); }); } } const result = await value.apply(this, props); store.set(key, koatty_lib.Helper.isJSONObj(result) ? JSON.stringify(result) : result, mergedOpt.timeout).catch((e) => { koatty_logger.DefaultLogger.error("Cache set error:" + e.message); }); return result; } else { return value.apply(this, props); } } }; } }, "method"); } __name(CacheAble, "CacheAble"); function CacheEvict(cacheName, opt = { delayedDoubleDeletion: true }) { return koatty_container.IOCContainer.createDecorator(({ target, methodName, descriptor, method, context }) => { const mergedOpt = { ...{ delayedDoubleDeletion: true }, ...opt }; if (context) { context.addInitializer?.(function() { const componentType = koatty_container.IOCContainer.getType(this.constructor); if (![ "SERVICE", "COMPONENT" ].includes(componentType)) { throw Error("This decorator only used in the service\u3001component class."); } }); const originalMethod = method; const funcParams = getArgs(originalMethod); const paramIndexes = getParamIndex(funcParams, mergedOpt.params || []); const invalidParams = []; (mergedOpt.params || []).forEach((param, index) => { if (paramIndexes[index] === -1) { invalidParams.push(param); } }); if (invalidParams.length > 0) { koatty_logger.DefaultLogger.Warn(`CacheEvict: Parameter(s) [${invalidParams.join(", ")}] not found in method ${String(methodName)}. These parameters will be ignored.`); } return async function(...props) { const store = await GetCacheStore().catch((e) => { koatty_logger.DefaultLogger.error("Get cache store instance failed." + e.message); return null; }); if (store) { const key = generateCacheKey(cacheName, paramIndexes, mergedOpt.params || [], props); const result = await originalMethod.apply(this, props); store.del(key).catch((e) => { koatty_logger.DefaultLogger.error("Cache delete error:" + e.message); }); if (mergedOpt.delayedDoubleDeletion) { const delayTime = mergedOpt.delayTime || 5e3; asyncDelayedExecution(() => { store.del(key).catch((e) => { koatty_logger.DefaultLogger.error("Cache double delete error:" + e.message); }); }, delayTime); } return result; } else { return originalMethod.apply(this, props); } }; } else { const componentType = koatty_container.IOCContainer.getType(target); if (![ "SERVICE", "COMPONENT" ].includes(componentType)) { throw Error("This decorator only used in the service\u3001component class."); } const { value, configurable, enumerable } = descriptor; const funcParams = getArgs(target[methodName]); const paramIndexes = getParamIndex(funcParams, mergedOpt.params || []); const invalidParams = []; (mergedOpt.params || []).forEach((param, index) => { if (paramIndexes[index] === -1) { invalidParams.push(param); } }); if (invalidParams.length > 0) { koatty_logger.DefaultLogger.Warn(`CacheEvict: Parameter(s) [${invalidParams.join(", ")}] not found in method ${String(methodName)}. These parameters will be ignored.`); } return { configurable, enumerable, writable: true, async value(...props) { const store = await GetCacheStore().catch((e) => { koatty_logger.DefaultLogger.error("Get cache store instance failed." + e.message); return null; }); if (store) { const key = generateCacheKey(cacheName, paramIndexes, mergedOpt.params || [], props); const result = await value.apply(this, props); store.del(key).catch((e) => { koatty_logger.DefaultLogger.error("Cache delete error:" + e.message); }); if (mergedOpt.delayedDoubleDeletion) { const delayTime = mergedOpt.delayTime || 5e3; asyncDelayedExecution(() => { store.del(key).catch((e) => { koatty_logger.DefaultLogger.error("Cache double delete error:" + e.message); }); }, delayTime); } return result; } else { return value.apply(this, props); } } }; } }, "method"); } __name(CacheEvict, "CacheEvict"); // src/index.ts var defaultOptions = { type: "memory", db: 0, timeout: 30 }; async function KoattyCached(options, app) { options = { ...defaultOptions, ...options }; app.once("appReady", async function() { await GetCacheStore(options); }); app.on("appStop", async function() { await CloseCacheStore(); }); } __name(KoattyCached, "KoattyCached"); exports.CacheAble = CacheAble; exports.CacheEvict = CacheEvict; exports.CloseCacheStore = CloseCacheStore; exports.GetCacheStore = GetCacheStore; exports.KoattyCached = KoattyCached; //# sourceMappingURL=index.js.map //# sourceMappingURL=index.js.map