koatty_cacheable 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,143 +1,101 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-06-23 01:59:54
3
+ * @Date: 2026-01-28 11:25:40
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
7
7
  */
8
8
  'use strict';
9
9
 
10
- var koatty_logger = require('koatty_logger');
11
10
  var koatty_lib = require('koatty_lib');
11
+ var koatty_logger = require('koatty_logger');
12
12
  var koatty_store = require('koatty_store');
13
13
  var koatty_container = require('koatty_container');
14
14
 
15
+ /*
16
+ * @Description:
17
+ * @Usage:
18
+ * @Author: richen
19
+ * @Date: 2024-11-07 16:00:02
20
+ * @LastEditTime: 2024-11-07 16:00:05
21
+ * @License: BSD (3-Clause)
22
+ * @Copyright (c): <richenlin(at)gmail.com>
23
+ */
15
24
  // storeCache
16
25
  const storeCache = {
17
26
  store: null
18
27
  };
28
+ // Promise to track initialization in progress
29
+ let initPromise = null;
19
30
  /**
20
31
  * get instances of storeCache
21
32
  *
22
33
  * @export
23
- * @param {Application} app
34
+ * @param {StoreOptions} options
24
35
  * @returns {*} {CacheStore}
25
36
  */
26
- async function GetCacheStore(app) {
37
+ async function GetCacheStore(options) {
38
+ // Return existing store if available
27
39
  if (storeCache.store && storeCache.store.getConnection) {
28
40
  return storeCache.store;
29
41
  }
30
- let opt = {
31
- type: "memory",
32
- db: 0,
33
- timeout: 30
34
- };
35
- if (app && koatty_lib.Helper.isFunction(app.config)) {
36
- opt = app.config("CacheStore") || app.config("CacheStore", "db");
37
- if (koatty_lib.Helper.isEmpty(opt)) {
38
- koatty_logger.DefaultLogger.Warn(`Missing CacheStore server configuration. Please write a configuration item with the key name 'CacheStore' in the db.ts file.`);
39
- }
40
- }
41
- storeCache.store = koatty_store.CacheStore.getInstance(opt);
42
- if (!koatty_lib.Helper.isFunction(storeCache.store.getConnection)) {
43
- throw Error(`CacheStore connection failed. `);
42
+ // If initialization is in progress, wait for it
43
+ if (initPromise) {
44
+ return initPromise;
44
45
  }
45
- await storeCache.store.client.getConnection();
46
- return storeCache.store;
47
- }
48
-
49
- /*
50
- * @Author: richen
51
- * @Date: 2020-07-06 19:53:43
52
- * @LastEditTime: 2025-06-23 15:53:46
53
- * @Description:
54
- * @Copyright (c) - <richenlin(at)gmail.com>
55
- */
56
- class CacheManager {
57
- static instance;
58
- cacheStore = null;
59
- defaultTimeout = 300;
60
- defaultDelayedDoubleDeletion = true;
61
- static getInstance() {
62
- if (!CacheManager.instance) {
63
- CacheManager.instance = new CacheManager();
46
+ if (koatty_lib.Helper.isEmpty(options)) {
47
+ if (!storeCache.store) {
48
+ koatty_logger.DefaultLogger.Warn(`CacheStore not initialized. Please call KoattyCached() first with proper options in your application startup.`);
64
49
  }
65
- return CacheManager.instance;
66
- }
67
- setCacheStore(store) {
68
- this.cacheStore = store;
69
- }
70
- getCacheStore() {
71
- return this.cacheStore;
72
- }
73
- setDefaultConfig(timeout, delayedDoubleDeletion) {
74
- if (timeout !== undefined)
75
- this.defaultTimeout = timeout;
76
- if (delayedDoubleDeletion !== undefined)
77
- this.defaultDelayedDoubleDeletion = delayedDoubleDeletion;
78
- }
79
- getDefaultTimeout() {
80
- return this.defaultTimeout;
81
- }
82
- getDefaultDelayedDoubleDeletion() {
83
- return this.defaultDelayedDoubleDeletion;
50
+ return storeCache.store || null;
84
51
  }
52
+ // Start initialization and track it
53
+ initPromise = (async () => {
54
+ try {
55
+ storeCache.store = koatty_store.CacheStore.getInstance(options);
56
+ if (!koatty_lib.Helper.isFunction(storeCache.store.getConnection)) {
57
+ throw Error(`CacheStore connection failed. `);
58
+ }
59
+ await storeCache.store.client.getConnection();
60
+ return storeCache.store;
61
+ }
62
+ finally {
63
+ // Clear init promise after completion
64
+ initPromise = null;
65
+ }
66
+ })();
67
+ return initPromise;
85
68
  }
86
-
87
- /**
88
- * @Description: Cache injector, unified processing of all cache decorators
89
- * @Usage:
90
- * @Author: richen
91
- * @Date: 2025-01-10 14:00:00
92
- * @LastEditTime: 2025-01-10 14:00:00
93
- * @License: BSD (3-Clause)
94
- * @Copyright (c): <richenlin(at)gmail.com>
95
- */
96
- // import { Helper } from 'koatty_lib';
97
- // Create logger instance
98
- const logger$1 = new koatty_logger.Logger();
99
69
  /**
100
- * Cache injector - initialize global cache manager and store
101
- * @param options Cache options
102
- * @param app Koatty application instance
70
+ * Close cache store connection for cleanup (mainly for testing)
103
71
  */
104
- async function injectCache(options, app) {
105
- try {
106
- logger$1.Debug('Initializing cache system...');
107
- // Get cache store instance
108
- const store = await GetCacheStore(app);
109
- if (!store) {
110
- logger$1.Warn('Cache store unavailable, cache system disabled');
111
- return;
72
+ async function CloseCacheStore() {
73
+ if (storeCache.store) {
74
+ try {
75
+ if (storeCache.store.client) {
76
+ const client = storeCache.store.client;
77
+ if (typeof client.quit === 'function') {
78
+ await client.quit();
79
+ }
80
+ else if (typeof client.close === 'function') {
81
+ await client.close();
82
+ }
83
+ }
84
+ }
85
+ catch {
86
+ // Ignore cleanup errors
112
87
  }
113
- // Initialize global cache manager
114
- const cacheManager = CacheManager.getInstance();
115
- cacheManager.setCacheStore(store);
116
- // Set default configuration
117
- cacheManager.setDefaultConfig(options.cacheTimeout || 300, options.delayedDoubleDeletion !== undefined ? options.delayedDoubleDeletion : true);
118
- logger$1.Info(`Cache system initialized successfully with timeout: ${options.cacheTimeout || 300}s`);
119
- }
120
- catch (error) {
121
- logger$1.Error('Cache system initialization failed:', error);
122
88
  }
123
- }
124
- /**
125
- * Close cache store connection
126
- * @param app Koatty application instance
127
- */
128
- async function closeCacheStore(_app) {
89
+ // Clear the CacheStore singleton instance
129
90
  try {
130
- logger$1.Debug('Closing cache store connection...');
131
- // Reset global cache manager
132
- const cacheManager = CacheManager.getInstance();
133
- const store = cacheManager.getCacheStore();
134
- await store?.close();
135
- cacheManager.setCacheStore(null);
136
- logger$1.Info('Cache store connection closed');
91
+ await koatty_store.CacheStore.clearAllInstances();
137
92
  }
138
- catch (error) {
139
- logger$1.Error('Error closing cache store connection:', error);
93
+ catch {
94
+ // Ignore cleanup errors
140
95
  }
96
+ // Always clear the cache
97
+ storeCache.store = null;
98
+ initPromise = null;
141
99
  }
142
100
 
143
101
  /* eslint-disable @typescript-eslint/no-unused-vars */
@@ -163,8 +121,11 @@ function getArgs(func) {
163
121
  if (args && args.length > 1) {
164
122
  // Split parameters into array and clean them
165
123
  return args[1].split(",").map(function (a) {
166
- // Remove inline comments and whitespace
167
- return a.replace(/\/\*.*\*\//, "").trim();
124
+ // Remove multi-line comments /* ... */ and single-line comments //
125
+ const param = a.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").trim();
126
+ // Extract parameter name (before : or = or end of string)
127
+ const match = param.match(/^(\w+)/);
128
+ return match ? match[1] : "";
168
129
  }).filter(function (ae) {
169
130
  // Filter out empty strings
170
131
  return ae;
@@ -230,17 +191,6 @@ async function asyncDelayedExecution(fn, ms) {
230
191
  * @Description:
231
192
  * @Copyright (c) - <richenlin(at)gmail.com>
232
193
  */
233
- // Create logger instance
234
- const logger = new koatty_logger.Logger();
235
- // Define cache decorator types
236
- exports.DecoratorType = void 0;
237
- (function (DecoratorType) {
238
- DecoratorType["CACHE_EVICT"] = "CACHE_EVICT";
239
- DecoratorType["CACHE_ABLE"] = "CACHE_ABLE";
240
- })(exports.DecoratorType || (exports.DecoratorType = {}));
241
- // IOC container key constant
242
- const COMPONENT_CACHE = "COMPONENT_CACHE";
243
- const CACHE_METADATA_KEY = "CACHE_METADATA_KEY";
244
194
  /**
245
195
  * Decorate this method to support caching.
246
196
  * The cache method returns a value to ensure that the next time
@@ -259,79 +209,70 @@ const CACHE_METADATA_KEY = "CACHE_METADATA_KEY";
259
209
  * Use the 'id' parameters of the method as cache subkeys, the cache expiration time 30s
260
210
  * @returns {MethodDecorator}
261
211
  */
262
- function CacheAble(cacheNameOrOpt, opt = {}) {
263
- // Handle overloaded parameters
264
- let cacheName;
265
- let options;
266
- if (typeof cacheNameOrOpt === 'string') {
267
- cacheName = cacheNameOrOpt;
268
- options = opt;
269
- }
270
- else {
271
- options = cacheNameOrOpt || {};
272
- cacheName = options.cacheName;
273
- }
212
+ function CacheAble(cacheName, opt = {
213
+ params: [],
214
+ timeout: 300,
215
+ }) {
274
216
  return (target, methodName, descriptor) => {
275
217
  const componentType = koatty_container.IOCContainer.getType(target);
276
218
  if (!["SERVICE", "COMPONENT"].includes(componentType)) {
277
219
  throw Error("This decorator only used in the service、component class.");
278
220
  }
279
- // Generate cache name if not provided
280
- const finalCacheName = cacheName || `${target.constructor.name}:${String(methodName)}`;
281
- // Get original method
282
- const originalMethod = descriptor.value;
283
- if (!koatty_lib.Helper.isFunction(originalMethod)) {
284
- throw new Error(`CacheAble decorator can only be applied to methods`);
285
- }
286
- // Create wrapped method
287
- descriptor.value = function (...args) {
288
- const cacheManager = CacheManager.getInstance();
289
- const store = cacheManager.getCacheStore();
290
- // If cache store is not available, execute original method directly
291
- if (!store) {
292
- logger.Debug(`Cache store not available for ${finalCacheName}, executing original method`);
293
- return originalMethod.apply(this, args);
221
+ const { value, configurable, enumerable } = descriptor;
222
+ const mergedOpt = { ...{ params: [], timeout: 300 }, ...opt };
223
+ // Get the parameter list of the method
224
+ const funcParams = getArgs(target[methodName]);
225
+ // Get the defined parameter location
226
+ const paramIndexes = getParamIndex(funcParams, mergedOpt.params || []);
227
+ // Validate parameters
228
+ const invalidParams = [];
229
+ (mergedOpt.params || []).forEach((param, index) => {
230
+ if (paramIndexes[index] === -1) {
231
+ invalidParams.push(param);
294
232
  }
295
- // Get method parameter list
296
- const funcParams = getArgs(originalMethod);
297
- // Get cache parameter positions
298
- const paramIndexes = getParamIndex(funcParams, options.params || []);
299
- return (async () => {
300
- try {
301
- // Generate cache key
302
- const key = generateCacheKey(finalCacheName, paramIndexes, options.params || [], args);
303
- // Try to get data from cache
304
- const cached = await store.get(key).catch((e) => {
305
- logger.Debug("Cache get error:" + e.message);
306
- return null;
233
+ });
234
+ if (invalidParams.length > 0) {
235
+ koatty_logger.DefaultLogger.Warn(`CacheAble: Parameter(s) [${invalidParams.join(", ")}] not found in method ${String(methodName)}. These parameters will be ignored.`);
236
+ }
237
+ descriptor = {
238
+ configurable,
239
+ enumerable,
240
+ writable: true,
241
+ async value(...props) {
242
+ const store = await GetCacheStore().catch((e) => {
243
+ koatty_logger.DefaultLogger.error("Get cache store instance failed." + e.message);
244
+ return null;
245
+ });
246
+ if (store) {
247
+ const key = generateCacheKey(cacheName, paramIndexes, mergedOpt.params, props);
248
+ const res = await store.get(key).catch((e) => {
249
+ koatty_logger.DefaultLogger.error("Cache get error:" + e.message);
307
250
  });
308
- if (!koatty_lib.Helper.isEmpty(cached)) {
309
- logger.Debug(`Cache hit for key: ${key}`);
251
+ if (!koatty_lib.Helper.isEmpty(res)) {
310
252
  try {
311
- return JSON.parse(cached);
253
+ return JSON.parse(res);
312
254
  }
313
- catch {
314
- // If parse fails, return as string (for simple values)
315
- return cached;
255
+ catch (e) {
256
+ const error = e;
257
+ koatty_logger.DefaultLogger.error("Cache JSON parse error:" + error.message);
258
+ // 如果解析失败,删除损坏的缓存,重新执行方法
259
+ store.del(key).catch((err) => {
260
+ koatty_logger.DefaultLogger.error("Cache del error after parse failure:" + err.message);
261
+ });
316
262
  }
317
263
  }
318
- logger.Debug(`Cache miss for key: ${key}`);
319
- // Execute original method
320
- const result = await originalMethod.apply(this, args);
321
- // Use decorator timeout if specified, otherwise use global default
322
- const timeout = options.timeout || cacheManager.getDefaultTimeout();
323
- // Asynchronously set cache
324
- store.set(key, koatty_lib.Helper.isJSONObj(result) ? JSON.stringify(result) : result, timeout).catch((e) => {
325
- logger.Debug("Cache set error:" + e.message);
264
+ const result = await value.apply(this, props);
265
+ // async refresh store
266
+ store.set(key, koatty_lib.Helper.isJSONObj(result) ? JSON.stringify(result) : result, mergedOpt.timeout).catch((e) => {
267
+ koatty_logger.DefaultLogger.error("Cache set error:" + e.message);
326
268
  });
327
269
  return result;
328
270
  }
329
- catch (error) {
330
- logger.Debug(`CacheAble wrapper error: ${error.message}`);
331
- // If cache operation fails, execute original method directly
332
- return originalMethod.apply(this, args);
271
+ else {
272
+ // tslint:disable-next-line: no-invalid-this
273
+ return value.apply(this, props);
333
274
  }
334
- })();
275
+ }
335
276
  };
336
277
  return descriptor;
337
278
  };
@@ -352,119 +293,91 @@ function CacheAble(cacheNameOrOpt, opt = {}) {
352
293
  * and clear the cache after the method executed
353
294
  * @returns
354
295
  */
355
- function CacheEvict(cacheNameOrOpt, opt = {}) {
356
- // Handle overloaded parameters
357
- let cacheName;
358
- let options;
359
- if (typeof cacheNameOrOpt === 'string') {
360
- cacheName = cacheNameOrOpt;
361
- options = opt;
362
- }
363
- else {
364
- options = cacheNameOrOpt || {};
365
- cacheName = options.cacheName;
366
- }
296
+ function CacheEvict(cacheName, opt = {
297
+ delayedDoubleDeletion: true,
298
+ }) {
367
299
  return (target, methodName, descriptor) => {
368
300
  const componentType = koatty_container.IOCContainer.getType(target);
369
301
  if (!["SERVICE", "COMPONENT"].includes(componentType)) {
370
302
  throw Error("This decorator only used in the service、component class.");
371
303
  }
372
- // Save class to IOC container for tracking
373
- koatty_container.IOCContainer.saveClass("COMPONENT", target, COMPONENT_CACHE);
374
- // Generate cache name if not provided
375
- const finalCacheName = cacheName || `${target.constructor.name}:${String(methodName)}`;
376
- // Get original method
377
- const originalMethod = descriptor.value;
378
- if (!koatty_lib.Helper.isFunction(originalMethod)) {
379
- throw new Error(`CacheEvict decorator can only be applied to methods`);
380
- }
381
- // Create wrapped method
382
- descriptor.value = function (...args) {
383
- const cacheManager = CacheManager.getInstance();
384
- const store = cacheManager.getCacheStore();
385
- // If cache store is not available, execute original method directly
386
- if (!store) {
387
- logger.Debug(`Cache store not available for ${finalCacheName}, executing original method`);
388
- return originalMethod.apply(this, args);
304
+ const { value, configurable, enumerable } = descriptor;
305
+ opt = { ...{ delayedDoubleDeletion: true, }, ...opt };
306
+ // Get the parameter list of the method
307
+ const funcParams = getArgs(target[methodName]);
308
+ // Get the defined parameter location
309
+ const paramIndexes = getParamIndex(funcParams, opt.params || []);
310
+ // Validate parameters
311
+ const invalidParams = [];
312
+ (opt.params || []).forEach((param, index) => {
313
+ if (paramIndexes[index] === -1) {
314
+ invalidParams.push(param);
389
315
  }
390
- // Get method parameter list
391
- const funcParams = getArgs(originalMethod);
392
- // Get cache parameter positions
393
- const paramIndexes = getParamIndex(funcParams, options.params || []);
394
- return (async () => {
395
- try {
396
- // Generate cache key
397
- const key = generateCacheKey(finalCacheName, paramIndexes, options.params || [], args);
398
- // Execute original method
399
- const result = await originalMethod.apply(this, args);
400
- // Immediately clear cache
316
+ });
317
+ if (invalidParams.length > 0) {
318
+ koatty_logger.DefaultLogger.Warn(`CacheEvict: Parameter(s) [${invalidParams.join(", ")}] not found in method ${String(methodName)}. These parameters will be ignored.`);
319
+ }
320
+ descriptor = {
321
+ configurable,
322
+ enumerable,
323
+ writable: true,
324
+ async value(...props) {
325
+ const store = await GetCacheStore().catch((e) => {
326
+ koatty_logger.DefaultLogger.error("Get cache store instance failed." + e.message);
327
+ return null;
328
+ });
329
+ if (store) {
330
+ const key = generateCacheKey(cacheName, paramIndexes, opt.params || [], props);
331
+ const result = await value.apply(this, props);
401
332
  store.del(key).catch((e) => {
402
- logger.Debug("Cache delete error:" + e.message);
333
+ koatty_logger.DefaultLogger.error("Cache delete error:" + e.message);
403
334
  });
404
- // Use decorator setting if specified, otherwise use global default
405
- const enableDelayedDeletion = options.delayedDoubleDeletion !== undefined
406
- ? options.delayedDoubleDeletion
407
- : cacheManager.getDefaultDelayedDoubleDeletion();
408
- // Delayed double deletion strategy
409
- if (enableDelayedDeletion !== false) {
410
- const delayTime = 5000;
335
+ if (opt.delayedDoubleDeletion) {
336
+ const delayTime = opt.delayTime || 5000;
411
337
  asyncDelayedExecution(() => {
412
338
  store.del(key).catch((e) => {
413
- logger.Debug("Cache double delete error:" + e.message);
339
+ koatty_logger.DefaultLogger.error("Cache double delete error:" + e.message);
414
340
  });
415
341
  }, delayTime);
416
342
  }
417
343
  return result;
418
344
  }
419
- catch (error) {
420
- logger.Debug(`CacheEvict wrapper error: ${error.message}`);
421
- // If cache operation fails, execute original method directly
422
- return originalMethod.apply(this, args);
345
+ else {
346
+ // If store is not available, execute method directly
347
+ return value.apply(this, props);
423
348
  }
424
- })();
349
+ }
425
350
  };
426
351
  return descriptor;
427
352
  };
428
353
  }
429
354
 
430
- /*
431
- * @Description:
432
- * @Usage:
433
- * @Author: richen
434
- * @Date: 2024-11-07 16:00:02
435
- * @LastEditTime: 2024-11-07 16:00:05
436
- * @License: BSD (3-Clause)
437
- * @Copyright (c): <richenlin(at)gmail.com>
438
- */
439
355
  /**
440
356
  * defaultOptions
441
357
  */
442
358
  const defaultOptions = {
443
- cacheTimeout: 300,
444
- delayedDoubleDeletion: true,
445
- redisConfig: {
446
- host: "localhost",
447
- port: 6379,
448
- password: "",
449
- db: 0,
450
- keyPrefix: "redlock:"
451
- }
359
+ type: "memory",
360
+ db: 0,
361
+ timeout: 30
452
362
  };
453
363
  /**
454
- * @param options - The options for the scheduled job
364
+ * @param options - The options for the cached options
455
365
  * @param app - The Koatty application instance
456
366
  */
457
- async function KoattyCache(options, app) {
367
+ async function KoattyCached(options, app) {
458
368
  options = { ...defaultOptions, ...options };
459
- // inject cache decorator
460
- await injectCache(options, app);
461
- // Register cleanup on app stop
462
- app.on('appStop', async () => {
463
- await closeCacheStore();
369
+ app.once("appReady", async function () {
370
+ // 初始化缓存存储
371
+ await GetCacheStore(options);
372
+ });
373
+ app.on("appStop", async function () {
374
+ // 关闭缓存存储
375
+ await CloseCacheStore();
464
376
  });
465
377
  }
466
378
 
467
- exports.CACHE_METADATA_KEY = CACHE_METADATA_KEY;
468
379
  exports.CacheAble = CacheAble;
469
380
  exports.CacheEvict = CacheEvict;
470
- exports.KoattyCache = KoattyCache;
381
+ exports.CloseCacheStore = CloseCacheStore;
382
+ exports.GetCacheStore = GetCacheStore;
383
+ exports.KoattyCached = KoattyCached;