koatty_cacheable 1.6.1 → 2.0.0

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,16 +1,144 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-06-09 13:04:37
3
+ * @Date: 2025-06-23 01:59:54
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_container = require('koatty_container');
11
- var koatty_lib = require('koatty_lib');
12
10
  var koatty_logger = require('koatty_logger');
11
+ var koatty_lib = require('koatty_lib');
13
12
  var koatty_store = require('koatty_store');
13
+ var koatty_container = require('koatty_container');
14
+
15
+ // storeCache
16
+ const storeCache = {
17
+ store: null
18
+ };
19
+ /**
20
+ * get instances of storeCache
21
+ *
22
+ * @export
23
+ * @param {Application} app
24
+ * @returns {*} {CacheStore}
25
+ */
26
+ async function GetCacheStore(app) {
27
+ if (storeCache.store && storeCache.store.getConnection) {
28
+ return storeCache.store;
29
+ }
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. `);
44
+ }
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();
64
+ }
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;
84
+ }
85
+ }
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
+ /**
100
+ * Cache injector - initialize global cache manager and store
101
+ * @param options Cache options
102
+ * @param app Koatty application instance
103
+ */
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;
112
+ }
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
+ }
123
+ }
124
+ /**
125
+ * Close cache store connection
126
+ * @param app Koatty application instance
127
+ */
128
+ async function closeCacheStore(_app) {
129
+ 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');
137
+ }
138
+ catch (error) {
139
+ logger$1.Error('Error closing cache store connection:', error);
140
+ }
141
+ }
14
142
 
15
143
  /* eslint-disable @typescript-eslint/no-unused-vars */
16
144
  /*
@@ -95,84 +223,6 @@ async function asyncDelayedExecution(fn, ms) {
95
223
  return fn();
96
224
  }
97
225
 
98
- /*
99
- * @Description:
100
- * @Usage:
101
- * @Author: richen
102
- * @Date: 2024-11-07 16:00:02
103
- * @LastEditTime: 2024-11-07 16:00:05
104
- * @License: BSD (3-Clause)
105
- * @Copyright (c): <richenlin(at)gmail.com>
106
- */
107
- // storeCache
108
- const storeCache = {
109
- store: null
110
- };
111
- /**
112
- * get instances of storeCache
113
- *
114
- * @export
115
- * @param {Application} app
116
- * @returns {*} {CacheStore}
117
- */
118
- async function GetCacheStore(app) {
119
- if (storeCache.store && storeCache.store.getConnection) {
120
- return storeCache.store;
121
- }
122
- let opt = {
123
- type: "memory",
124
- db: 0,
125
- timeout: 30
126
- };
127
- if (app && koatty_lib.Helper.isFunction(app.config)) {
128
- opt = app.config("CacheStore") || app.config("CacheStore", "db");
129
- if (koatty_lib.Helper.isEmpty(opt)) {
130
- koatty_logger.DefaultLogger.Warn(`Missing CacheStore server configuration. Please write a configuration item with the key name 'CacheStore' in the db.ts file.`);
131
- }
132
- }
133
- storeCache.store = koatty_store.CacheStore.getInstance(opt);
134
- if (!koatty_lib.Helper.isFunction(storeCache.store.getConnection)) {
135
- throw Error(`CacheStore connection failed. `);
136
- }
137
- await storeCache.store.client.getConnection();
138
- return storeCache.store;
139
- }
140
- /**
141
- * initiation CacheStore connection and client.
142
- *
143
- */
144
- async function InitCacheStore() {
145
- if (storeCache.store) {
146
- return;
147
- }
148
- const app = koatty_container.IOCContainer.getApp();
149
- app?.once("appReady", async () => {
150
- await GetCacheStore(app);
151
- });
152
- }
153
- /**
154
- * Close cache store connection for cleanup (mainly for testing)
155
- */
156
- async function CloseCacheStore() {
157
- if (storeCache.store && storeCache.store.client) {
158
- try {
159
- const client = storeCache.store.client;
160
- if (typeof client.quit === 'function') {
161
- await client.quit();
162
- }
163
- else if (typeof client.close === 'function') {
164
- await client.close();
165
- }
166
- }
167
- catch {
168
- // Ignore cleanup errors
169
- }
170
- finally {
171
- storeCache.store = null;
172
- }
173
- }
174
- }
175
-
176
226
  /*
177
227
  * @Author: richen
178
228
  * @Date: 2020-07-06 19:53:43
@@ -180,6 +230,17 @@ async function CloseCacheStore() {
180
230
  * @Description:
181
231
  * @Copyright (c) - <richenlin(at)gmail.com>
182
232
  */
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";
183
244
  /**
184
245
  * Decorate this method to support caching.
185
246
  * The cache method returns a value to ensure that the next time
@@ -198,53 +259,80 @@ async function CloseCacheStore() {
198
259
  * Use the 'id' parameters of the method as cache subkeys, the cache expiration time 30s
199
260
  * @returns {MethodDecorator}
200
261
  */
201
- function CacheAble(cacheName, opt = {
202
- params: [],
203
- timeout: 300,
204
- }) {
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
+ }
205
274
  return (target, methodName, descriptor) => {
206
275
  const componentType = koatty_container.IOCContainer.getType(target);
207
276
  if (!["SERVICE", "COMPONENT"].includes(componentType)) {
208
277
  throw Error("This decorator only used in the service、component class.");
209
278
  }
210
- const { value, configurable, enumerable } = descriptor;
211
- const mergedOpt = { ...{ params: [], timeout: 300 }, ...opt };
212
- // Get the parameter list of the method
213
- const funcParams = getArgs(target[methodName]);
214
- // Get the defined parameter location
215
- const paramIndexes = getParamIndex(funcParams, mergedOpt.params || []);
216
- descriptor = {
217
- configurable,
218
- enumerable,
219
- writable: true,
220
- async value(...props) {
221
- const store = await GetCacheStore(this.app).catch((e) => {
222
- koatty_logger.DefaultLogger.error("Get cache store instance failed." + e.message);
223
- return null;
224
- });
225
- if (store) {
226
- const key = generateCacheKey(cacheName, paramIndexes, mergedOpt.params, props);
227
- const res = await store.get(key).catch((e) => {
228
- koatty_logger.DefaultLogger.error("Cache get error:" + e.message);
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);
294
+ }
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;
229
307
  });
230
- if (!koatty_lib.Helper.isEmpty(res)) {
231
- return JSON.parse(res);
308
+ if (!koatty_lib.Helper.isEmpty(cached)) {
309
+ logger.Debug(`Cache hit for key: ${key}`);
310
+ try {
311
+ return JSON.parse(cached);
312
+ }
313
+ catch {
314
+ // If parse fails, return as string (for simple values)
315
+ return cached;
316
+ }
232
317
  }
233
- const result = await value.apply(this, props);
234
- // async refresh store
235
- store.set(key, koatty_lib.Helper.isJSONObj(result) ? JSON.stringify(result) : result, mergedOpt.timeout).catch((e) => {
236
- koatty_logger.DefaultLogger.error("Cache set error:" + e.message);
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);
237
326
  });
238
327
  return result;
239
328
  }
240
- else {
241
- // tslint:disable-next-line: no-invalid-this
242
- return value.apply(this, props);
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);
243
333
  }
244
- }
334
+ })();
245
335
  };
246
- // bind app_ready hook event
247
- InitCacheStore();
248
336
  return descriptor;
249
337
  };
250
338
  }
@@ -264,59 +352,119 @@ function CacheAble(cacheName, opt = {
264
352
  * and clear the cache after the method executed
265
353
  * @returns
266
354
  */
267
- function CacheEvict(cacheName, opt = {
268
- delayedDoubleDeletion: true,
269
- }) {
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
+ }
270
367
  return (target, methodName, descriptor) => {
271
368
  const componentType = koatty_container.IOCContainer.getType(target);
272
369
  if (!["SERVICE", "COMPONENT"].includes(componentType)) {
273
370
  throw Error("This decorator only used in the service、component class.");
274
371
  }
275
- const { value, configurable, enumerable } = descriptor;
276
- opt = { ...{ delayedDoubleDeletion: true, }, ...opt };
277
- // Get the parameter list of the method
278
- const funcParams = getArgs(target[methodName]);
279
- // Get the defined parameter location
280
- const paramIndexes = getParamIndex(funcParams, opt.params || []);
281
- descriptor = {
282
- configurable,
283
- enumerable,
284
- writable: true,
285
- async value(...props) {
286
- const store = await GetCacheStore(this.app).catch((e) => {
287
- koatty_logger.DefaultLogger.error("Get cache store instance failed." + e.message);
288
- return null;
289
- });
290
- if (store) {
291
- const key = generateCacheKey(cacheName, paramIndexes, opt.params || [], props);
292
- const result = await value.apply(this, props);
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);
389
+ }
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
293
401
  store.del(key).catch((e) => {
294
- koatty_logger.DefaultLogger.error("Cache delete error:" + e.message);
402
+ logger.Debug("Cache delete error:" + e.message);
295
403
  });
296
- if (opt.delayedDoubleDeletion) {
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) {
297
410
  const delayTime = 5000;
298
411
  asyncDelayedExecution(() => {
299
412
  store.del(key).catch((e) => {
300
- koatty_logger.DefaultLogger.error("Cache double delete error:" + e.message);
413
+ logger.Debug("Cache double delete error:" + e.message);
301
414
  });
302
415
  }, delayTime);
303
416
  }
304
417
  return result;
305
418
  }
306
- else {
307
- // If store is not available, execute method directly
308
- return value.apply(this, props);
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);
309
423
  }
310
- }
424
+ })();
311
425
  };
312
- // bind app_ready hook event
313
- InitCacheStore();
314
426
  return descriptor;
315
427
  };
316
428
  }
317
429
 
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
+ /**
440
+ * defaultOptions
441
+ */
442
+ 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
+ }
452
+ };
453
+ /**
454
+ * @param options - The options for the scheduled job
455
+ * @param app - The Koatty application instance
456
+ */
457
+ async function KoattyCache(options, app) {
458
+ 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();
464
+ });
465
+ }
466
+
467
+ exports.CACHE_METADATA_KEY = CACHE_METADATA_KEY;
318
468
  exports.CacheAble = CacheAble;
319
469
  exports.CacheEvict = CacheEvict;
320
- exports.CloseCacheStore = CloseCacheStore;
321
- exports.GetCacheStore = GetCacheStore;
322
- exports.InitCacheStore = InitCacheStore;
470
+ exports.KoattyCache = KoattyCache;