koatty_cacheable 1.5.0 → 1.6.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/.rollup.config.js +48 -51
- package/CHANGELOG.md +4 -0
- package/README.md +152 -21
- package/dist/README.md +152 -21
- package/dist/index.d.ts +13 -2
- package/dist/index.js +305 -246
- package/dist/index.mjs +304 -247
- package/dist/package.json +26 -20
- package/package.json +26 -20
- package/target/npmlist.json +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,259 +1,316 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* @Author: richen
|
|
3
|
-
* @Date:
|
|
3
|
+
* @Date: 2025-06-09 13:04:37
|
|
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';
|
|
8
9
|
import { Helper } from 'koatty_lib';
|
|
9
10
|
import { DefaultLogger } from 'koatty_logger';
|
|
10
11
|
import { CacheStore } from 'koatty_store';
|
|
11
|
-
import { IOCContainer } from 'koatty_container';
|
|
12
12
|
|
|
13
|
-
/*
|
|
14
|
-
|
|
15
|
-
* @
|
|
16
|
-
* @
|
|
17
|
-
* @
|
|
18
|
-
* @
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
*
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
13
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
14
|
+
/*
|
|
15
|
+
* @Description:
|
|
16
|
+
* @Usage:
|
|
17
|
+
* @Author: richen
|
|
18
|
+
* @Date: 2024-11-07 13:54:24
|
|
19
|
+
* @LastEditTime: 2024-11-07 15:25:36
|
|
20
|
+
* @License: BSD (3-Clause)
|
|
21
|
+
* @Copyright (c): <richenlin(at)gmail.com>
|
|
22
|
+
*/
|
|
23
|
+
const longKey = 128;
|
|
24
|
+
/**
|
|
25
|
+
* Extract parameter names from function signature
|
|
26
|
+
* @param func The function to extract parameters from
|
|
27
|
+
* @returns Array of parameter names
|
|
28
|
+
*/
|
|
29
|
+
function getArgs(func) {
|
|
30
|
+
try {
|
|
31
|
+
// Match function parameters in parentheses
|
|
32
|
+
const args = func.toString().match(/.*?\(([^)]*)\)/);
|
|
33
|
+
if (args && args.length > 1) {
|
|
34
|
+
// Split parameters into array and clean them
|
|
35
|
+
return args[1].split(",").map(function (a) {
|
|
36
|
+
// Remove inline comments and whitespace
|
|
37
|
+
return a.replace(/\/\*.*\*\//, "").trim();
|
|
38
|
+
}).filter(function (ae) {
|
|
39
|
+
// Filter out empty strings
|
|
40
|
+
return ae;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
// Return empty array if parsing fails
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get parameter indexes based on parameter names
|
|
52
|
+
* @param funcParams Function parameter names
|
|
53
|
+
* @param params Target parameter names to find indexes for
|
|
54
|
+
* @returns Array of parameter indexes (-1 if not found)
|
|
55
|
+
*/
|
|
56
|
+
function getParamIndex(funcParams, params) {
|
|
57
|
+
return params.map(param => funcParams.indexOf(param));
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Generate cache key based on cache name and parameters
|
|
61
|
+
* @param cacheName base cache name
|
|
62
|
+
* @param paramIndexes parameter indexes
|
|
63
|
+
* @param paramNames parameter names
|
|
64
|
+
* @param props method arguments
|
|
65
|
+
* @returns generated cache key
|
|
66
|
+
*/
|
|
67
|
+
function generateCacheKey(cacheName, paramIndexes, paramNames, props) {
|
|
68
|
+
let key = cacheName;
|
|
69
|
+
for (let i = 0; i < paramIndexes.length; i++) {
|
|
70
|
+
const paramIndex = paramIndexes[i];
|
|
71
|
+
if (paramIndex >= 0 && props[paramIndex] !== undefined) {
|
|
72
|
+
key += `:${paramNames[i]}:${Helper.toString(props[paramIndex])}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return key.length > longKey ? Helper.murmurHash(key) : key;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Create a delay promise
|
|
79
|
+
* @param ms Delay time in milliseconds
|
|
80
|
+
* @returns Promise that resolves after the specified delay
|
|
81
|
+
*/
|
|
82
|
+
function delay(ms) {
|
|
83
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Execute a function after a specified delay
|
|
87
|
+
* @param fn Function to execute
|
|
88
|
+
* @param ms Delay time in milliseconds
|
|
89
|
+
* @returns Promise that resolves with the function result
|
|
90
|
+
*/
|
|
91
|
+
async function asyncDelayedExecution(fn, ms) {
|
|
92
|
+
await delay(ms);
|
|
93
|
+
return fn();
|
|
94
|
+
}
|
|
95
|
+
|
|
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
|
+
/*
|
|
175
|
+
* @Author: richen
|
|
176
|
+
* @Date: 2020-07-06 19:53:43
|
|
177
|
+
* @LastEditTime: 2024-11-07 15:53:46
|
|
178
|
+
* @Description:
|
|
179
|
+
* @Copyright (c) - <richenlin(at)gmail.com>
|
|
180
|
+
*/
|
|
181
|
+
/**
|
|
182
|
+
* Decorate this method to support caching.
|
|
183
|
+
* The cache method returns a value to ensure that the next time
|
|
184
|
+
* the method is executed with the same parameters, the results can be obtained
|
|
185
|
+
* directly from the cache without the need to execute the method again.
|
|
186
|
+
* CacheStore server config defined in db.ts.
|
|
187
|
+
*
|
|
188
|
+
* @export
|
|
189
|
+
* @param {string} cacheName cache name
|
|
190
|
+
* @param {CacheAbleOpt} [opt] cache options
|
|
191
|
+
* e.g:
|
|
192
|
+
* {
|
|
193
|
+
* params: ["id"],
|
|
194
|
+
* timeout: 30
|
|
195
|
+
* }
|
|
196
|
+
* Use the 'id' parameters of the method as cache subkeys, the cache expiration time 30s
|
|
197
|
+
* @returns {MethodDecorator}
|
|
198
|
+
*/
|
|
199
|
+
function CacheAble(cacheName, opt = {
|
|
200
|
+
params: [],
|
|
201
|
+
timeout: 300,
|
|
202
|
+
}) {
|
|
203
|
+
return (target, methodName, descriptor) => {
|
|
204
|
+
const componentType = IOCContainer.getType(target);
|
|
205
|
+
if (!["SERVICE", "COMPONENT"].includes(componentType)) {
|
|
206
|
+
throw Error("This decorator only used in the service、component class.");
|
|
207
|
+
}
|
|
208
|
+
const { value, configurable, enumerable } = descriptor;
|
|
209
|
+
const mergedOpt = { ...{ params: [], timeout: 300 }, ...opt };
|
|
210
|
+
// Get the parameter list of the method
|
|
211
|
+
const funcParams = getArgs(target[methodName]);
|
|
212
|
+
// Get the defined parameter location
|
|
213
|
+
const paramIndexes = getParamIndex(funcParams, mergedOpt.params || []);
|
|
214
|
+
descriptor = {
|
|
215
|
+
configurable,
|
|
216
|
+
enumerable,
|
|
217
|
+
writable: true,
|
|
218
|
+
async value(...props) {
|
|
219
|
+
const store = await GetCacheStore(this.app).catch((e) => {
|
|
220
|
+
DefaultLogger.error("Get cache store instance failed." + e.message);
|
|
221
|
+
return null;
|
|
222
|
+
});
|
|
223
|
+
if (store) {
|
|
224
|
+
const key = generateCacheKey(cacheName, paramIndexes, mergedOpt.params, props);
|
|
225
|
+
const res = await store.get(key).catch((e) => {
|
|
226
|
+
DefaultLogger.error("Cache get error:" + e.message);
|
|
227
|
+
});
|
|
228
|
+
if (!Helper.isEmpty(res)) {
|
|
229
|
+
return JSON.parse(res);
|
|
230
|
+
}
|
|
231
|
+
const result = await value.apply(this, props);
|
|
232
|
+
// async refresh store
|
|
233
|
+
store.set(key, Helper.isJSONObj(result) ? JSON.stringify(result) : result, mergedOpt.timeout).catch((e) => {
|
|
234
|
+
DefaultLogger.error("Cache set error:" + e.message);
|
|
235
|
+
});
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// tslint:disable-next-line: no-invalid-this
|
|
240
|
+
return value.apply(this, props);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
// bind app_ready hook event
|
|
245
|
+
InitCacheStore();
|
|
246
|
+
return descriptor;
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Decorating the execution of this method will trigger a cache clear operation.
|
|
251
|
+
* CacheStore server config defined in db.ts.
|
|
252
|
+
*
|
|
253
|
+
* @export
|
|
254
|
+
* @param {string} cacheName cacheName cache name
|
|
255
|
+
* @param {CacheEvictOpt} [opt] cache options
|
|
256
|
+
* e.g:
|
|
257
|
+
* {
|
|
258
|
+
* params: ["id"],
|
|
259
|
+
* delayedDoubleDeletion: true
|
|
260
|
+
* }
|
|
261
|
+
* Use the 'id' parameters of the method as cache subkeys,
|
|
262
|
+
* and clear the cache after the method executed
|
|
263
|
+
* @returns
|
|
264
|
+
*/
|
|
265
|
+
function CacheEvict(cacheName, opt = {
|
|
266
|
+
delayedDoubleDeletion: true,
|
|
267
|
+
}) {
|
|
268
|
+
return (target, methodName, descriptor) => {
|
|
269
|
+
const componentType = IOCContainer.getType(target);
|
|
270
|
+
if (!["SERVICE", "COMPONENT"].includes(componentType)) {
|
|
271
|
+
throw Error("This decorator only used in the service、component class.");
|
|
272
|
+
}
|
|
273
|
+
const { value, configurable, enumerable } = descriptor;
|
|
274
|
+
opt = { ...{ delayedDoubleDeletion: true, }, ...opt };
|
|
275
|
+
// Get the parameter list of the method
|
|
276
|
+
const funcParams = getArgs(target[methodName]);
|
|
277
|
+
// Get the defined parameter location
|
|
278
|
+
const paramIndexes = getParamIndex(funcParams, opt.params || []);
|
|
279
|
+
descriptor = {
|
|
280
|
+
configurable,
|
|
281
|
+
enumerable,
|
|
282
|
+
writable: true,
|
|
283
|
+
async value(...props) {
|
|
284
|
+
const store = await GetCacheStore(this.app).catch((e) => {
|
|
285
|
+
DefaultLogger.error("Get cache store instance failed." + e.message);
|
|
286
|
+
return null;
|
|
287
|
+
});
|
|
288
|
+
if (store) {
|
|
289
|
+
const key = generateCacheKey(cacheName, paramIndexes, opt.params || [], props);
|
|
290
|
+
const result = await value.apply(this, props);
|
|
291
|
+
store.del(key).catch((e) => {
|
|
292
|
+
DefaultLogger.error("Cache delete error:" + e.message);
|
|
293
|
+
});
|
|
294
|
+
if (opt.delayedDoubleDeletion) {
|
|
295
|
+
const delayTime = 5000;
|
|
296
|
+
asyncDelayedExecution(() => {
|
|
297
|
+
store.del(key).catch((e) => {
|
|
298
|
+
DefaultLogger.error("Cache double delete error:" + e.message);
|
|
299
|
+
});
|
|
300
|
+
}, delayTime);
|
|
301
|
+
}
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
// If store is not available, execute method directly
|
|
306
|
+
return value.apply(this, props);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
// bind app_ready hook event
|
|
311
|
+
InitCacheStore();
|
|
312
|
+
return descriptor;
|
|
313
|
+
};
|
|
257
314
|
}
|
|
258
315
|
|
|
259
|
-
export { CacheAble, CacheEvict, GetCacheStore };
|
|
316
|
+
export { CacheAble, CacheEvict, CloseCacheStore, GetCacheStore, InitCacheStore };
|