koatty_cacheable
Version:
Cacheable for koatty.
385 lines (380 loc) • 13.7 kB
JavaScript
'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