koatty_cacheable 1.5.0 → 1.6.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/.rollup.config.js +59 -62
- package/CHANGELOG.md +39 -37
- package/LICENSE +29 -29
- package/README.md +56 -56
- package/dist/LICENSE +29 -29
- package/dist/README.md +56 -56
- package/dist/index.d.ts +2 -2
- package/dist/index.js +259 -246
- package/dist/index.mjs +259 -246
- package/dist/package.json +98 -93
- package/package.json +98 -93
- package/target/npmlist.json +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,259 +1,272 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* @Author: richen
|
|
3
|
-
* @Date: 2024-
|
|
3
|
+
* @Date: 2024-11-07 16:06:44
|
|
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
|
-
|
|
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
|
+
// storeCache
|
|
24
|
+
const storeCache = {
|
|
25
|
+
store: null
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* get instances of storeCache
|
|
29
|
+
*
|
|
30
|
+
* @export
|
|
31
|
+
* @param {Application} app
|
|
32
|
+
* @returns {*} {CacheStore}
|
|
33
|
+
*/
|
|
34
|
+
async function GetCacheStore(app) {
|
|
35
|
+
if (storeCache.store && storeCache.store.getConnection) {
|
|
36
|
+
return storeCache.store;
|
|
37
|
+
}
|
|
38
|
+
let opt = {
|
|
39
|
+
type: "memory",
|
|
40
|
+
db: 0,
|
|
41
|
+
timeout: 30
|
|
42
|
+
};
|
|
43
|
+
if (app && Helper.isFunction(app.config)) {
|
|
44
|
+
opt = app.config("CacheStore") || app.config("CacheStore", "db");
|
|
45
|
+
if (Helper.isEmpty(opt)) {
|
|
46
|
+
DefaultLogger.Warn(`Missing CacheStore server configuration. Please write a configuration item with the key name 'CacheStore' in the db.ts file.`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
storeCache.store = CacheStore.getInstance(opt);
|
|
50
|
+
if (!Helper.isFunction(storeCache.store.getConnection)) {
|
|
51
|
+
throw Error(`CacheStore connection failed. `);
|
|
52
|
+
}
|
|
53
|
+
await storeCache.store.client.getConnection();
|
|
54
|
+
return storeCache.store;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* initiation CacheStore connection and client.
|
|
58
|
+
*
|
|
59
|
+
*/
|
|
60
|
+
async function InitCacheStore() {
|
|
61
|
+
if (storeCache.store) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const app = IOCContainer.getApp();
|
|
65
|
+
app?.once("appReady", async () => {
|
|
66
|
+
await GetCacheStore(app);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* @description:
|
|
71
|
+
* @param {*} func
|
|
72
|
+
* @return {*}
|
|
73
|
+
*/
|
|
74
|
+
function getArgs(func) {
|
|
75
|
+
// 首先匹配函数括弧里的参数
|
|
76
|
+
const args = func.toString().match(/.*?\(([^)]*)\)/);
|
|
77
|
+
if (args.length > 1) {
|
|
78
|
+
// 分解参数成数组
|
|
79
|
+
return args[1].split(",").map(function (a) {
|
|
80
|
+
// 去空格和内联注释
|
|
81
|
+
return a.replace(/\/\*.*\*\//, "").trim();
|
|
82
|
+
}).filter(function (ae) {
|
|
83
|
+
// 确保没有undefineds
|
|
84
|
+
return ae;
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* @description:
|
|
91
|
+
* @param {string[]} funcParams
|
|
92
|
+
* @param {string[]} params
|
|
93
|
+
* @return {*}
|
|
94
|
+
*/
|
|
95
|
+
function getParamIndex(funcParams, params) {
|
|
96
|
+
return params.map(param => funcParams.indexOf(param));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
*
|
|
100
|
+
* @param ms
|
|
101
|
+
* @returns
|
|
102
|
+
*/
|
|
103
|
+
function delay(ms) {
|
|
104
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* async delayed execution func
|
|
108
|
+
* @param fn
|
|
109
|
+
* @param ms
|
|
110
|
+
* @returns
|
|
111
|
+
*/
|
|
112
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
113
|
+
async function asyncDelayedExecution(fn, ms) {
|
|
114
|
+
await delay(ms); // delay ms second
|
|
115
|
+
return fn();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* @Author: richen
|
|
120
|
+
* @Date: 2020-07-06 19:53:43
|
|
121
|
+
* @LastEditTime: 2024-11-07 15:53:46
|
|
122
|
+
* @Description:
|
|
123
|
+
* @Copyright (c) - <richenlin(at)gmail.com>
|
|
124
|
+
*/
|
|
125
|
+
const longKey = 128;
|
|
126
|
+
/**
|
|
127
|
+
* Decorate this method to support caching.
|
|
128
|
+
* The cache method returns a value to ensure that the next time
|
|
129
|
+
* the method is executed with the same parameters, the results can be obtained
|
|
130
|
+
* directly from the cache without the need to execute the method again.
|
|
131
|
+
* CacheStore server config defined in db.ts.
|
|
132
|
+
*
|
|
133
|
+
* @export
|
|
134
|
+
* @param {string} cacheName cache name
|
|
135
|
+
* @param {CacheAbleOpt} [opt] cache options
|
|
136
|
+
* e.g:
|
|
137
|
+
* {
|
|
138
|
+
* params: ["id"],
|
|
139
|
+
* timeout: 30
|
|
140
|
+
* }
|
|
141
|
+
* Use the 'id' parameters of the method as cache subkeys, the cache expiration time 30s
|
|
142
|
+
* @returns {MethodDecorator}
|
|
143
|
+
*/
|
|
144
|
+
function CacheAble(cacheName, opt = {
|
|
145
|
+
params: [],
|
|
146
|
+
timeout: 300,
|
|
147
|
+
}) {
|
|
148
|
+
return (target, methodName, descriptor) => {
|
|
149
|
+
const componentType = IOCContainer.getType(target);
|
|
150
|
+
if (!["SERVICE", "COMPONENT"].includes(componentType)) {
|
|
151
|
+
throw Error("This decorator only used in the service、component class.");
|
|
152
|
+
}
|
|
153
|
+
const { value, configurable, enumerable } = descriptor;
|
|
154
|
+
const mergedOpt = { ...{ params: [], timeout: 300 }, ...opt };
|
|
155
|
+
// Get the parameter list of the method
|
|
156
|
+
const funcParams = getArgs(target[methodName]);
|
|
157
|
+
// Get the defined parameter location
|
|
158
|
+
const paramIndexes = getParamIndex(funcParams, opt.params);
|
|
159
|
+
descriptor = {
|
|
160
|
+
configurable,
|
|
161
|
+
enumerable,
|
|
162
|
+
writable: true,
|
|
163
|
+
async value(...props) {
|
|
164
|
+
const store = await GetCacheStore(this.app).catch((e) => {
|
|
165
|
+
DefaultLogger.Error("Get cache store instance failed." + e.message);
|
|
166
|
+
return null;
|
|
167
|
+
});
|
|
168
|
+
if (store) {
|
|
169
|
+
let key = cacheName;
|
|
170
|
+
for (const item of paramIndexes) {
|
|
171
|
+
if (props[item] !== undefined) {
|
|
172
|
+
key += `:${mergedOpt.params[item]}:${Helper.toString(props[item])}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
key = key.length > longKey ? Helper.murmurHash(key) : key;
|
|
176
|
+
const res = await store.get(key).catch((e) => {
|
|
177
|
+
DefaultLogger.error("Cache get error:" + e.message);
|
|
178
|
+
});
|
|
179
|
+
if (!Helper.isEmpty(res)) {
|
|
180
|
+
return JSON.parse(res);
|
|
181
|
+
}
|
|
182
|
+
const result = await value.apply(this, props);
|
|
183
|
+
// async refresh store
|
|
184
|
+
store.set(key, Helper.isJSONObj(result) ? JSON.stringify(result) : result, mergedOpt.timeout).catch((e) => {
|
|
185
|
+
DefaultLogger.error("Cache set error:" + e.message);
|
|
186
|
+
});
|
|
187
|
+
return result;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
// tslint:disable-next-line: no-invalid-this
|
|
191
|
+
return value.apply(this, props);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
// bind app_ready hook event
|
|
196
|
+
InitCacheStore();
|
|
197
|
+
return descriptor;
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Decorating the execution of this method will trigger a cache clear operation.
|
|
202
|
+
* CacheStore server config defined in db.ts.
|
|
203
|
+
*
|
|
204
|
+
* @export
|
|
205
|
+
* @param {string} cacheName cacheName cache name
|
|
206
|
+
* @param {CacheEvictOpt} [opt] cache options
|
|
207
|
+
* e.g:
|
|
208
|
+
* {
|
|
209
|
+
* params: ["id"],
|
|
210
|
+
* delayedDoubleDeletion: true
|
|
211
|
+
* }
|
|
212
|
+
* Use the 'id' parameters of the method as cache subkeys,
|
|
213
|
+
* and clear the cache after the method executed
|
|
214
|
+
* @returns
|
|
215
|
+
*/
|
|
216
|
+
function CacheEvict(cacheName, opt = {
|
|
217
|
+
delayedDoubleDeletion: true,
|
|
218
|
+
}) {
|
|
219
|
+
return (target, methodName, descriptor) => {
|
|
220
|
+
const componentType = IOCContainer.getType(target);
|
|
221
|
+
if (!["SERVICE", "COMPONENT"].includes(componentType)) {
|
|
222
|
+
throw Error("This decorator only used in the service、component class.");
|
|
223
|
+
}
|
|
224
|
+
const { value, configurable, enumerable } = descriptor;
|
|
225
|
+
opt = { ...{ delayedDoubleDeletion: true, }, ...opt };
|
|
226
|
+
// Get the parameter list of the method
|
|
227
|
+
const funcParams = getArgs(target[methodName]);
|
|
228
|
+
// Get the defined parameter location
|
|
229
|
+
const paramIndexes = getParamIndex(funcParams, opt.params);
|
|
230
|
+
descriptor = {
|
|
231
|
+
configurable,
|
|
232
|
+
enumerable,
|
|
233
|
+
writable: true,
|
|
234
|
+
async value(...props) {
|
|
235
|
+
const store = await GetCacheStore(this.app).catch((e) => {
|
|
236
|
+
DefaultLogger.Error("Get cache store instance failed." + e.message);
|
|
237
|
+
return null;
|
|
238
|
+
});
|
|
239
|
+
if (store) {
|
|
240
|
+
let key = cacheName;
|
|
241
|
+
for (const item of paramIndexes) {
|
|
242
|
+
if (props[item] !== undefined) {
|
|
243
|
+
key += `:${opt.params[item]}:${Helper.toString(props[item])}`;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
key = key.length > longKey ? Helper.murmurHash(key) : key;
|
|
247
|
+
const result = await value.apply(this, props);
|
|
248
|
+
store.del(key).catch((e) => {
|
|
249
|
+
DefaultLogger.Error("Cache delete error:" + e.message);
|
|
250
|
+
});
|
|
251
|
+
if (opt.delayedDoubleDeletion) {
|
|
252
|
+
asyncDelayedExecution(() => {
|
|
253
|
+
store.del(key).catch((e) => {
|
|
254
|
+
DefaultLogger.error("Cache double delete error:" + e.message);
|
|
255
|
+
});
|
|
256
|
+
}, 5000);
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
// tslint:disable-next-line: no-invalid-this
|
|
261
|
+
return value.apply(this, props);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
// bind app_ready hook event
|
|
267
|
+
InitCacheStore();
|
|
268
|
+
return descriptor;
|
|
269
|
+
};
|
|
257
270
|
}
|
|
258
271
|
|
|
259
272
|
export { CacheAble, CacheEvict, GetCacheStore };
|