koatty_cacheable 1.6.1 → 2.0.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
+ ### [2.0.1](https://github.com/thinkkoa/koatty_cacheable/compare/v1.6.1...v2.0.1) (2025-11-02)
6
+
7
+ ## [2.0.0](https://github.com/thinkkoa/koatty_cacheable/compare/v1.6.1...v2.0.0) (2025-11-02)
8
+
5
9
  ### [1.6.1](https://github.com/thinkkoa/koatty_cacheable/compare/v1.6.0...v1.6.1) (2025-06-09)
6
10
 
7
11
  ## [1.6.0](https://github.com/thinkkoa/koatty_cacheable/compare/v1.5.0...v1.6.0) (2024-11-07)
package/README.md CHANGED
@@ -20,33 +20,61 @@ Koatty框架的 CacheAble, CacheEvict 缓存装饰器支持库,提供方法级
20
20
  npm install koatty_cacheable
21
21
  ```
22
22
 
23
- ## 配置
23
+ ## 快速开始
24
24
 
25
- koatty 项目的 `db.ts` 配置文件中添加缓存配置:
25
+ ### 1. 生成插件模板
26
+
27
+ 使用 Koatty CLI 生成插件模板:
28
+
29
+ ```bash
30
+ kt plugin Cacheable
31
+ ```
32
+
33
+ 创建 `src/plugin/Cacheable.ts`:
34
+
35
+ ```typescript
36
+ import { Plugin, IPlugin, App } from "koatty";
37
+ import { KoattyCached } from "koatty_cacheable";
38
+
39
+ @Plugin()
40
+ export class Cacheable implements IPlugin {
41
+ run(options: any, app: App) {
42
+ return KoattyCached(options, app);
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### 2. 配置插件
48
+
49
+ 更新 `src/config/plugin.ts`:
26
50
 
27
51
  ```typescript
28
52
  export default {
29
- // ... 其他配置
30
-
31
- "CacheStore": {
32
- type: "memory", // 缓存类型: "redis" 或 "memory",默认为 "memory"
33
- // Redis 配置 (当 type 为 "redis" 时)
34
- // key_prefix: "koatty",
35
- // host: '127.0.0.1',
36
- // port: 6379,
37
- // name: "",
38
- // username: "",
39
- // password: "",
40
- // db: 0,
41
- // timeout: 30,
42
- // pool_size: 10,
43
- // conn_timeout: 30
44
- },
45
-
46
- // ... 其他配置
53
+ list: ["Cacheable"], // 插件加载顺序
54
+ config: {
55
+ Cacheable: {
56
+ type: "memory", // 缓存类型: "redis" 或 "memory",默认为 "memory"
57
+ db: 0,
58
+ timeout: 30,
59
+ // Redis 配置 (当 type 为 "redis" 时)
60
+ // key_prefix: "koatty",
61
+ // host: '127.0.0.1',
62
+ // port: 6379,
63
+ // name: "",
64
+ // username: "",
65
+ // password: "",
66
+ // pool_size: 10,
67
+ // conn_timeout: 30
68
+ }
69
+ }
47
70
  };
48
71
  ```
49
72
 
73
+ **注意事项**:
74
+ - 插件会在应用启动时自动初始化缓存
75
+ - 必须在插件配置中提供正确的缓存配置
76
+ - 如果缓存未正确初始化,装饰器方法会直接执行而不进行缓存(优雅降级)
77
+
50
78
  ## 使用方法
51
79
 
52
80
  ### 基本用法
@@ -178,10 +206,12 @@ export class ProductService {
178
206
 
179
207
  ## 注意事项
180
208
 
181
- 1. 装饰器只能用于 `SERVICE` `COMPONENT` 类型的类
182
- 2. 被装饰的方法必须是异步方法(返回 Promise)
183
- 3. 缓存的数据会自动进行 JSON 序列化/反序列化
184
- 4. 如果缓存服务不可用,方法会正常执行,不会抛出错误
209
+ 1. **初始化顺序**: 必须先调用 `KoattyCached()` 初始化缓存,然后再使用装饰器。建议在应用启动时(如 `init()` 方法中)进行初始化
210
+ 2. 装饰器只能用于 `SERVICE` 和 `COMPONENT` 类型的类
211
+ 3. 被装饰的方法必须是异步方法(返回 Promise)
212
+ 4. 缓存的数据会自动进行 JSON 序列化/反序列化
213
+ 5. 如果缓存服务不可用,方法会正常执行,不会抛出错误(优雅降级)
214
+ 6. 缓存键长度超过 128 字符时会自动使用 murmur hash 进行压缩
185
215
 
186
216
  ## 许可证
187
217
 
package/dist/README.md CHANGED
@@ -20,33 +20,61 @@ Koatty框架的 CacheAble, CacheEvict 缓存装饰器支持库,提供方法级
20
20
  npm install koatty_cacheable
21
21
  ```
22
22
 
23
- ## 配置
23
+ ## 快速开始
24
24
 
25
- koatty 项目的 `db.ts` 配置文件中添加缓存配置:
25
+ ### 1. 生成插件模板
26
+
27
+ 使用 Koatty CLI 生成插件模板:
28
+
29
+ ```bash
30
+ kt plugin Cacheable
31
+ ```
32
+
33
+ 创建 `src/plugin/Cacheable.ts`:
34
+
35
+ ```typescript
36
+ import { Plugin, IPlugin, App } from "koatty";
37
+ import { KoattyCached } from "koatty_cacheable";
38
+
39
+ @Plugin()
40
+ export class Cacheable implements IPlugin {
41
+ run(options: any, app: App) {
42
+ return KoattyCached(options, app);
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### 2. 配置插件
48
+
49
+ 更新 `src/config/plugin.ts`:
26
50
 
27
51
  ```typescript
28
52
  export default {
29
- // ... 其他配置
30
-
31
- "CacheStore": {
32
- type: "memory", // 缓存类型: "redis" 或 "memory",默认为 "memory"
33
- // Redis 配置 (当 type 为 "redis" 时)
34
- // key_prefix: "koatty",
35
- // host: '127.0.0.1',
36
- // port: 6379,
37
- // name: "",
38
- // username: "",
39
- // password: "",
40
- // db: 0,
41
- // timeout: 30,
42
- // pool_size: 10,
43
- // conn_timeout: 30
44
- },
45
-
46
- // ... 其他配置
53
+ list: ["Cacheable"], // 插件加载顺序
54
+ config: {
55
+ Cacheable: {
56
+ type: "memory", // 缓存类型: "redis" 或 "memory",默认为 "memory"
57
+ db: 0,
58
+ timeout: 30,
59
+ // Redis 配置 (当 type 为 "redis" 时)
60
+ // key_prefix: "koatty",
61
+ // host: '127.0.0.1',
62
+ // port: 6379,
63
+ // name: "",
64
+ // username: "",
65
+ // password: "",
66
+ // pool_size: 10,
67
+ // conn_timeout: 30
68
+ }
69
+ }
47
70
  };
48
71
  ```
49
72
 
73
+ **注意事项**:
74
+ - 插件会在应用启动时自动初始化缓存
75
+ - 必须在插件配置中提供正确的缓存配置
76
+ - 如果缓存未正确初始化,装饰器方法会直接执行而不进行缓存(优雅降级)
77
+
50
78
  ## 使用方法
51
79
 
52
80
  ### 基本用法
@@ -178,10 +206,12 @@ export class ProductService {
178
206
 
179
207
  ## 注意事项
180
208
 
181
- 1. 装饰器只能用于 `SERVICE` `COMPONENT` 类型的类
182
- 2. 被装饰的方法必须是异步方法(返回 Promise)
183
- 3. 缓存的数据会自动进行 JSON 序列化/反序列化
184
- 4. 如果缓存服务不可用,方法会正常执行,不会抛出错误
209
+ 1. **初始化顺序**: 必须先调用 `KoattyCached()` 初始化缓存,然后再使用装饰器。建议在应用启动时(如 `init()` 方法中)进行初始化
210
+ 2. 装饰器只能用于 `SERVICE` 和 `COMPONENT` 类型的类
211
+ 3. 被装饰的方法必须是异步方法(返回 Promise)
212
+ 4. 缓存的数据会自动进行 JSON 序列化/反序列化
213
+ 5. 如果缓存服务不可用,方法会正常执行,不会抛出错误(优雅降级)
214
+ 6. 缓存键长度超过 128 字符时会自动使用 murmur hash 进行压缩
185
215
 
186
216
  ## 许可证
187
217
 
package/dist/index.d.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-06-09 13:04:42
3
+ * @Date: 2025-11-02 09:20:03
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
7
7
  */
8
- import { Application } from 'koatty_container';
9
8
  import { CacheStore } from 'koatty_store';
9
+ import { Koatty } from 'koatty_core';
10
+ import { StoreOptions } from 'koatty_store';
10
11
 
11
12
  /**
12
13
  * Decorate this method to support caching.
@@ -62,6 +63,7 @@ export declare function CacheEvict(cacheName: string, opt?: CacheEvictOpt): (tar
62
63
  export declare interface CacheEvictOpt {
63
64
  params?: string[];
64
65
  delayedDoubleDeletion?: boolean;
66
+ delayTime?: number;
65
67
  }
66
68
 
67
69
  /**
@@ -73,15 +75,15 @@ export declare function CloseCacheStore(): Promise<void>;
73
75
  * get instances of storeCache
74
76
  *
75
77
  * @export
76
- * @param {Application} app
78
+ * @param {StoreOptions} options
77
79
  * @returns {*} {CacheStore}
78
80
  */
79
- export declare function GetCacheStore(app?: Application): Promise<CacheStore>;
81
+ export declare function GetCacheStore(options?: StoreOptions): Promise<CacheStore>;
80
82
 
81
83
  /**
82
- * initiation CacheStore connection and client.
83
- *
84
+ * @param options - The options for the cached options
85
+ * @param app - The Koatty application instance
84
86
  */
85
- export declare function InitCacheStore(): Promise<void>;
87
+ export declare function KoattyCached(options: StoreOptions, app: Koatty): Promise<void>;
86
88
 
87
89
  export { }
package/dist/index.js CHANGED
@@ -1,16 +1,102 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-06-09 13:04:37
3
+ * @Date: 2025-11-02 09:19:59
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
10
  var koatty_lib = require('koatty_lib');
12
11
  var koatty_logger = require('koatty_logger');
13
12
  var koatty_store = require('koatty_store');
13
+ var koatty_container = require('koatty_container');
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
+ */
24
+ // storeCache
25
+ const storeCache = {
26
+ store: null
27
+ };
28
+ // Promise to track initialization in progress
29
+ let initPromise = null;
30
+ /**
31
+ * get instances of storeCache
32
+ *
33
+ * @export
34
+ * @param {StoreOptions} options
35
+ * @returns {*} {CacheStore}
36
+ */
37
+ async function GetCacheStore(options) {
38
+ // Return existing store if available
39
+ if (storeCache.store && storeCache.store.getConnection) {
40
+ return storeCache.store;
41
+ }
42
+ // If initialization is in progress, wait for it
43
+ if (initPromise) {
44
+ return initPromise;
45
+ }
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.`);
49
+ }
50
+ return storeCache.store || null;
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;
68
+ }
69
+ /**
70
+ * Close cache store connection for cleanup (mainly for testing)
71
+ */
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
87
+ }
88
+ }
89
+ // Clear the CacheStore singleton instance
90
+ try {
91
+ await koatty_store.CacheStore.clearAllInstances();
92
+ }
93
+ catch {
94
+ // Ignore cleanup errors
95
+ }
96
+ // Always clear the cache
97
+ storeCache.store = null;
98
+ initPromise = null;
99
+ }
14
100
 
15
101
  /* eslint-disable @typescript-eslint/no-unused-vars */
16
102
  /*
@@ -35,8 +121,11 @@ function getArgs(func) {
35
121
  if (args && args.length > 1) {
36
122
  // Split parameters into array and clean them
37
123
  return args[1].split(",").map(function (a) {
38
- // Remove inline comments and whitespace
39
- 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] : "";
40
129
  }).filter(function (ae) {
41
130
  // Filter out empty strings
42
131
  return ae;
@@ -95,84 +184,6 @@ async function asyncDelayedExecution(fn, ms) {
95
184
  return fn();
96
185
  }
97
186
 
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
187
  /*
177
188
  * @Author: richen
178
189
  * @Date: 2020-07-06 19:53:43
@@ -213,12 +224,22 @@ function CacheAble(cacheName, opt = {
213
224
  const funcParams = getArgs(target[methodName]);
214
225
  // Get the defined parameter location
215
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);
232
+ }
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
+ }
216
237
  descriptor = {
217
238
  configurable,
218
239
  enumerable,
219
240
  writable: true,
220
241
  async value(...props) {
221
- const store = await GetCacheStore(this.app).catch((e) => {
242
+ const store = await GetCacheStore().catch((e) => {
222
243
  koatty_logger.DefaultLogger.error("Get cache store instance failed." + e.message);
223
244
  return null;
224
245
  });
@@ -228,7 +249,17 @@ function CacheAble(cacheName, opt = {
228
249
  koatty_logger.DefaultLogger.error("Cache get error:" + e.message);
229
250
  });
230
251
  if (!koatty_lib.Helper.isEmpty(res)) {
231
- return JSON.parse(res);
252
+ try {
253
+ return JSON.parse(res);
254
+ }
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
+ });
262
+ }
232
263
  }
233
264
  const result = await value.apply(this, props);
234
265
  // async refresh store
@@ -243,8 +274,6 @@ function CacheAble(cacheName, opt = {
243
274
  }
244
275
  }
245
276
  };
246
- // bind app_ready hook event
247
- InitCacheStore();
248
277
  return descriptor;
249
278
  };
250
279
  }
@@ -278,12 +307,22 @@ function CacheEvict(cacheName, opt = {
278
307
  const funcParams = getArgs(target[methodName]);
279
308
  // Get the defined parameter location
280
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);
315
+ }
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
+ }
281
320
  descriptor = {
282
321
  configurable,
283
322
  enumerable,
284
323
  writable: true,
285
324
  async value(...props) {
286
- const store = await GetCacheStore(this.app).catch((e) => {
325
+ const store = await GetCacheStore().catch((e) => {
287
326
  koatty_logger.DefaultLogger.error("Get cache store instance failed." + e.message);
288
327
  return null;
289
328
  });
@@ -294,7 +333,7 @@ function CacheEvict(cacheName, opt = {
294
333
  koatty_logger.DefaultLogger.error("Cache delete error:" + e.message);
295
334
  });
296
335
  if (opt.delayedDoubleDeletion) {
297
- const delayTime = 5000;
336
+ const delayTime = opt.delayTime || 5000;
298
337
  asyncDelayedExecution(() => {
299
338
  store.del(key).catch((e) => {
300
339
  koatty_logger.DefaultLogger.error("Cache double delete error:" + e.message);
@@ -309,14 +348,36 @@ function CacheEvict(cacheName, opt = {
309
348
  }
310
349
  }
311
350
  };
312
- // bind app_ready hook event
313
- InitCacheStore();
314
351
  return descriptor;
315
352
  };
316
353
  }
317
354
 
355
+ /**
356
+ * defaultOptions
357
+ */
358
+ const defaultOptions = {
359
+ type: "memory",
360
+ db: 0,
361
+ timeout: 30
362
+ };
363
+ /**
364
+ * @param options - The options for the cached options
365
+ * @param app - The Koatty application instance
366
+ */
367
+ async function KoattyCached(options, app) {
368
+ options = { ...defaultOptions, ...options };
369
+ app.once("appReady", async function () {
370
+ // 初始化缓存存储
371
+ await GetCacheStore(options);
372
+ });
373
+ app.on("appStop", async function () {
374
+ // 关闭缓存存储
375
+ await CloseCacheStore();
376
+ });
377
+ }
378
+
318
379
  exports.CacheAble = CacheAble;
319
380
  exports.CacheEvict = CacheEvict;
320
381
  exports.CloseCacheStore = CloseCacheStore;
321
382
  exports.GetCacheStore = GetCacheStore;
322
- exports.InitCacheStore = InitCacheStore;
383
+ exports.KoattyCached = KoattyCached;
package/dist/index.mjs CHANGED
@@ -1,14 +1,100 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2025-06-09 13:04:37
3
+ * @Date: 2025-11-02 09:19:59
4
4
  * @License: BSD (3-Clause)
5
5
  * @Copyright (c) - <richenlin(at)gmail.com>
6
6
  * @HomePage: https://koatty.org/
7
7
  */
8
- import { IOCContainer } from 'koatty_container';
9
8
  import { Helper } from 'koatty_lib';
10
9
  import { DefaultLogger } from 'koatty_logger';
11
10
  import { CacheStore } from 'koatty_store';
11
+ import { IOCContainer } from 'koatty_container';
12
+
13
+ /*
14
+ * @Description:
15
+ * @Usage:
16
+ * @Author: richen
17
+ * @Date: 2024-11-07 16:00:02
18
+ * @LastEditTime: 2024-11-07 16:00:05
19
+ * @License: BSD (3-Clause)
20
+ * @Copyright (c): <richenlin(at)gmail.com>
21
+ */
22
+ // storeCache
23
+ const storeCache = {
24
+ store: null
25
+ };
26
+ // Promise to track initialization in progress
27
+ let initPromise = null;
28
+ /**
29
+ * get instances of storeCache
30
+ *
31
+ * @export
32
+ * @param {StoreOptions} options
33
+ * @returns {*} {CacheStore}
34
+ */
35
+ async function GetCacheStore(options) {
36
+ // Return existing store if available
37
+ if (storeCache.store && storeCache.store.getConnection) {
38
+ return storeCache.store;
39
+ }
40
+ // If initialization is in progress, wait for it
41
+ if (initPromise) {
42
+ return initPromise;
43
+ }
44
+ if (Helper.isEmpty(options)) {
45
+ if (!storeCache.store) {
46
+ DefaultLogger.Warn(`CacheStore not initialized. Please call KoattyCached() first with proper options in your application startup.`);
47
+ }
48
+ return storeCache.store || null;
49
+ }
50
+ // Start initialization and track it
51
+ initPromise = (async () => {
52
+ try {
53
+ storeCache.store = CacheStore.getInstance(options);
54
+ if (!Helper.isFunction(storeCache.store.getConnection)) {
55
+ throw Error(`CacheStore connection failed. `);
56
+ }
57
+ await storeCache.store.client.getConnection();
58
+ return storeCache.store;
59
+ }
60
+ finally {
61
+ // Clear init promise after completion
62
+ initPromise = null;
63
+ }
64
+ })();
65
+ return initPromise;
66
+ }
67
+ /**
68
+ * Close cache store connection for cleanup (mainly for testing)
69
+ */
70
+ async function CloseCacheStore() {
71
+ if (storeCache.store) {
72
+ try {
73
+ if (storeCache.store.client) {
74
+ const client = storeCache.store.client;
75
+ if (typeof client.quit === 'function') {
76
+ await client.quit();
77
+ }
78
+ else if (typeof client.close === 'function') {
79
+ await client.close();
80
+ }
81
+ }
82
+ }
83
+ catch {
84
+ // Ignore cleanup errors
85
+ }
86
+ }
87
+ // Clear the CacheStore singleton instance
88
+ try {
89
+ await CacheStore.clearAllInstances();
90
+ }
91
+ catch {
92
+ // Ignore cleanup errors
93
+ }
94
+ // Always clear the cache
95
+ storeCache.store = null;
96
+ initPromise = null;
97
+ }
12
98
 
13
99
  /* eslint-disable @typescript-eslint/no-unused-vars */
14
100
  /*
@@ -33,8 +119,11 @@ function getArgs(func) {
33
119
  if (args && args.length > 1) {
34
120
  // Split parameters into array and clean them
35
121
  return args[1].split(",").map(function (a) {
36
- // Remove inline comments and whitespace
37
- return a.replace(/\/\*.*\*\//, "").trim();
122
+ // Remove multi-line comments /* ... */ and single-line comments //
123
+ const param = a.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/.*$/gm, "").trim();
124
+ // Extract parameter name (before : or = or end of string)
125
+ const match = param.match(/^(\w+)/);
126
+ return match ? match[1] : "";
38
127
  }).filter(function (ae) {
39
128
  // Filter out empty strings
40
129
  return ae;
@@ -93,84 +182,6 @@ async function asyncDelayedExecution(fn, ms) {
93
182
  return fn();
94
183
  }
95
184
 
96
- /*
97
- * @Description:
98
- * @Usage:
99
- * @Author: richen
100
- * @Date: 2024-11-07 16:00:02
101
- * @LastEditTime: 2024-11-07 16:00:05
102
- * @License: BSD (3-Clause)
103
- * @Copyright (c): <richenlin(at)gmail.com>
104
- */
105
- // storeCache
106
- const storeCache = {
107
- store: null
108
- };
109
- /**
110
- * get instances of storeCache
111
- *
112
- * @export
113
- * @param {Application} app
114
- * @returns {*} {CacheStore}
115
- */
116
- async function GetCacheStore(app) {
117
- if (storeCache.store && storeCache.store.getConnection) {
118
- return storeCache.store;
119
- }
120
- let opt = {
121
- type: "memory",
122
- db: 0,
123
- timeout: 30
124
- };
125
- if (app && Helper.isFunction(app.config)) {
126
- opt = app.config("CacheStore") || app.config("CacheStore", "db");
127
- if (Helper.isEmpty(opt)) {
128
- DefaultLogger.Warn(`Missing CacheStore server configuration. Please write a configuration item with the key name 'CacheStore' in the db.ts file.`);
129
- }
130
- }
131
- storeCache.store = CacheStore.getInstance(opt);
132
- if (!Helper.isFunction(storeCache.store.getConnection)) {
133
- throw Error(`CacheStore connection failed. `);
134
- }
135
- await storeCache.store.client.getConnection();
136
- return storeCache.store;
137
- }
138
- /**
139
- * initiation CacheStore connection and client.
140
- *
141
- */
142
- async function InitCacheStore() {
143
- if (storeCache.store) {
144
- return;
145
- }
146
- const app = IOCContainer.getApp();
147
- app?.once("appReady", async () => {
148
- await GetCacheStore(app);
149
- });
150
- }
151
- /**
152
- * Close cache store connection for cleanup (mainly for testing)
153
- */
154
- async function CloseCacheStore() {
155
- if (storeCache.store && storeCache.store.client) {
156
- try {
157
- const client = storeCache.store.client;
158
- if (typeof client.quit === 'function') {
159
- await client.quit();
160
- }
161
- else if (typeof client.close === 'function') {
162
- await client.close();
163
- }
164
- }
165
- catch {
166
- // Ignore cleanup errors
167
- }
168
- finally {
169
- storeCache.store = null;
170
- }
171
- }
172
- }
173
-
174
185
  /*
175
186
  * @Author: richen
176
187
  * @Date: 2020-07-06 19:53:43
@@ -211,12 +222,22 @@ function CacheAble(cacheName, opt = {
211
222
  const funcParams = getArgs(target[methodName]);
212
223
  // Get the defined parameter location
213
224
  const paramIndexes = getParamIndex(funcParams, mergedOpt.params || []);
225
+ // Validate parameters
226
+ const invalidParams = [];
227
+ (mergedOpt.params || []).forEach((param, index) => {
228
+ if (paramIndexes[index] === -1) {
229
+ invalidParams.push(param);
230
+ }
231
+ });
232
+ if (invalidParams.length > 0) {
233
+ DefaultLogger.Warn(`CacheAble: Parameter(s) [${invalidParams.join(", ")}] not found in method ${String(methodName)}. These parameters will be ignored.`);
234
+ }
214
235
  descriptor = {
215
236
  configurable,
216
237
  enumerable,
217
238
  writable: true,
218
239
  async value(...props) {
219
- const store = await GetCacheStore(this.app).catch((e) => {
240
+ const store = await GetCacheStore().catch((e) => {
220
241
  DefaultLogger.error("Get cache store instance failed." + e.message);
221
242
  return null;
222
243
  });
@@ -226,7 +247,17 @@ function CacheAble(cacheName, opt = {
226
247
  DefaultLogger.error("Cache get error:" + e.message);
227
248
  });
228
249
  if (!Helper.isEmpty(res)) {
229
- return JSON.parse(res);
250
+ try {
251
+ return JSON.parse(res);
252
+ }
253
+ catch (e) {
254
+ const error = e;
255
+ DefaultLogger.error("Cache JSON parse error:" + error.message);
256
+ // 如果解析失败,删除损坏的缓存,重新执行方法
257
+ store.del(key).catch((err) => {
258
+ DefaultLogger.error("Cache del error after parse failure:" + err.message);
259
+ });
260
+ }
230
261
  }
231
262
  const result = await value.apply(this, props);
232
263
  // async refresh store
@@ -241,8 +272,6 @@ function CacheAble(cacheName, opt = {
241
272
  }
242
273
  }
243
274
  };
244
- // bind app_ready hook event
245
- InitCacheStore();
246
275
  return descriptor;
247
276
  };
248
277
  }
@@ -276,12 +305,22 @@ function CacheEvict(cacheName, opt = {
276
305
  const funcParams = getArgs(target[methodName]);
277
306
  // Get the defined parameter location
278
307
  const paramIndexes = getParamIndex(funcParams, opt.params || []);
308
+ // Validate parameters
309
+ const invalidParams = [];
310
+ (opt.params || []).forEach((param, index) => {
311
+ if (paramIndexes[index] === -1) {
312
+ invalidParams.push(param);
313
+ }
314
+ });
315
+ if (invalidParams.length > 0) {
316
+ DefaultLogger.Warn(`CacheEvict: Parameter(s) [${invalidParams.join(", ")}] not found in method ${String(methodName)}. These parameters will be ignored.`);
317
+ }
279
318
  descriptor = {
280
319
  configurable,
281
320
  enumerable,
282
321
  writable: true,
283
322
  async value(...props) {
284
- const store = await GetCacheStore(this.app).catch((e) => {
323
+ const store = await GetCacheStore().catch((e) => {
285
324
  DefaultLogger.error("Get cache store instance failed." + e.message);
286
325
  return null;
287
326
  });
@@ -292,7 +331,7 @@ function CacheEvict(cacheName, opt = {
292
331
  DefaultLogger.error("Cache delete error:" + e.message);
293
332
  });
294
333
  if (opt.delayedDoubleDeletion) {
295
- const delayTime = 5000;
334
+ const delayTime = opt.delayTime || 5000;
296
335
  asyncDelayedExecution(() => {
297
336
  store.del(key).catch((e) => {
298
337
  DefaultLogger.error("Cache double delete error:" + e.message);
@@ -307,10 +346,32 @@ function CacheEvict(cacheName, opt = {
307
346
  }
308
347
  }
309
348
  };
310
- // bind app_ready hook event
311
- InitCacheStore();
312
349
  return descriptor;
313
350
  };
314
351
  }
315
352
 
316
- export { CacheAble, CacheEvict, CloseCacheStore, GetCacheStore, InitCacheStore };
353
+ /**
354
+ * defaultOptions
355
+ */
356
+ const defaultOptions = {
357
+ type: "memory",
358
+ db: 0,
359
+ timeout: 30
360
+ };
361
+ /**
362
+ * @param options - The options for the cached options
363
+ * @param app - The Koatty application instance
364
+ */
365
+ async function KoattyCached(options, app) {
366
+ options = { ...defaultOptions, ...options };
367
+ app.once("appReady", async function () {
368
+ // 初始化缓存存储
369
+ await GetCacheStore(options);
370
+ });
371
+ app.on("appStop", async function () {
372
+ // 关闭缓存存储
373
+ await CloseCacheStore();
374
+ });
375
+ }
376
+
377
+ export { CacheAble, CacheEvict, CloseCacheStore, GetCacheStore, KoattyCached };
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koatty_cacheable",
3
- "version": "1.6.1",
3
+ "version": "2.0.1",
4
4
  "description": "Cacheable for koatty.",
5
5
  "scripts": {
6
6
  "build": "npm run build:js && npm run build:dts && npm run build:doc && npm run build:cp",
@@ -87,13 +87,16 @@
87
87
  },
88
88
  "dependencies": {
89
89
  "koatty_container": "^1.x.x",
90
+ "koatty_core": "2.x.x",
90
91
  "koatty_lib": "^1.x.x",
91
92
  "koatty_logger": "^2.x.x",
92
- "koatty_store": "^1.8.0"
93
+ "koatty_store": "^1.x.x"
93
94
  },
94
95
  "peerDependencies": {
95
96
  "koatty_container": "^1.x.x",
97
+ "koatty_core": "2.x.x",
96
98
  "koatty_lib": "^1.x.x",
97
- "koatty_logger": "^2.x.x"
99
+ "koatty_logger": "^2.x.x",
100
+ "koatty_store": "^1.x.x"
98
101
  }
99
102
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "koatty_cacheable",
3
- "version": "1.6.1",
3
+ "version": "2.0.1",
4
4
  "description": "Cacheable for koatty.",
5
5
  "scripts": {
6
6
  "build": "npm run build:js && npm run build:dts && npm run build:doc && npm run build:cp",
@@ -87,13 +87,16 @@
87
87
  },
88
88
  "dependencies": {
89
89
  "koatty_container": "^1.x.x",
90
+ "koatty_core": "2.x.x",
90
91
  "koatty_lib": "^1.x.x",
91
92
  "koatty_logger": "^2.x.x",
92
- "koatty_store": "^1.8.0"
93
+ "koatty_store": "^1.x.x"
93
94
  },
94
95
  "peerDependencies": {
95
96
  "koatty_container": "^1.x.x",
97
+ "koatty_core": "2.x.x",
96
98
  "koatty_lib": "^1.x.x",
97
- "koatty_logger": "^2.x.x"
99
+ "koatty_logger": "^2.x.x",
100
+ "koatty_store": "^1.x.x"
98
101
  }
99
102
  }