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/dist/index.mjs CHANGED
@@ -1,259 +1,272 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2024-04-01 15:47:40
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
- * @Author: richen
15
- * @Date: 2020-07-06 19:53:43
16
- * @LastEditTime: 2024-04-01 15:44:15
17
- * @Description:
18
- * @Copyright (c) - <richenlin(at)gmail.com>
19
- */
20
- const longKey = 128;
21
- // storeCache
22
- const storeCache = {
23
- store: null
24
- };
25
- /**
26
- * get instances of storeCache
27
- *
28
- * @export
29
- * @param {Application} app
30
- * @returns {*} {CacheStore}
31
- */
32
- async function GetCacheStore(app) {
33
- var _a, _b;
34
- if (storeCache.store && storeCache.store.getConnection) {
35
- return storeCache.store;
36
- }
37
- const opt = (_b = (_a = app.config("CacheStore")) !== null && _a !== void 0 ? _a : app.config("CacheStore", "db")) !== null && _b !== void 0 ? _b : {};
38
- if (Helper.isEmpty(opt)) {
39
- DefaultLogger.Warn(`Missing CacheStore server configuration. Please write a configuration item with the key name 'CacheStore' in the db.ts file.`);
40
- }
41
- storeCache.store = CacheStore.getInstance(opt);
42
- if (!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
- * initiation CacheStore connection and client.
50
- *
51
- */
52
- async function InitCacheStore() {
53
- if (storeCache.store !== null) {
54
- return;
55
- }
56
- const app = IOCContainer.getApp();
57
- app && app.once("appReady", async function () {
58
- await GetCacheStore(app);
59
- });
60
- }
61
- /**
62
- * Decorate this method to support caching.
63
- * The cache method returns a value to ensure that the next time
64
- * the method is executed with the same parameters, the results can be obtained
65
- * directly from the cache without the need to execute the method again.
66
- * CacheStore server config defined in db.ts.
67
- *
68
- * @export
69
- * @param {string} cacheName cache name
70
- * @param {CacheAbleOpt} [opt] cache options
71
- * e.g:
72
- * {
73
- * params: ["id"],
74
- * timeout: 30
75
- * }
76
- * Use the 'id' parameters of the method as cache subkeys, the cache expiration time 30s
77
- * @returns {MethodDecorator}
78
- */
79
- function CacheAble(cacheName, opt = {
80
- params: [],
81
- timeout: 300,
82
- }) {
83
- return (target, methodName, descriptor) => {
84
- const componentType = IOCContainer.getType(target);
85
- if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
86
- throw Error("This decorator only used in the service、component class.");
87
- }
88
- const { value, configurable, enumerable } = descriptor;
89
- opt = {
90
- ...{
91
- params: [],
92
- timeout: 300,
93
- }, ...opt
94
- };
95
- // 获取定义的参数位置
96
- const paramIndexes = getParamIndex(opt.params);
97
- descriptor = {
98
- configurable,
99
- enumerable,
100
- writable: true,
101
- async value(...props) {
102
- let cacheFlag = true;
103
- const store = await GetCacheStore(this.app).catch(() => {
104
- cacheFlag = false;
105
- DefaultLogger.Error("Get cache store instance failed.");
106
- return null;
107
- });
108
- if (cacheFlag) {
109
- // tslint:disable-next-line: one-variable-per-declaration
110
- let key = cacheName;
111
- if (props && props.length > 0) {
112
- for (const item of paramIndexes) {
113
- if (props[item] !== undefined) {
114
- const value = Helper.toString(props[item]);
115
- key += `:${opt.params[item]}:${value}`;
116
- }
117
- }
118
- // 防止key超长
119
- if (key.length > longKey) {
120
- key = Helper.murmurHash(key);
121
- }
122
- }
123
- let res = await store.get(key).catch(() => null);
124
- if (!Helper.isEmpty(res)) {
125
- return JSON.parse(res);
126
- }
127
- // tslint:disable-next-line: no-invalid-this
128
- res = await value.apply(this, props);
129
- // prevent cache penetration
130
- if (Helper.isTrueEmpty(res)) {
131
- res = "";
132
- }
133
- // async set store
134
- store.set(key, JSON.stringify(res), opt.timeout).catch(() => null);
135
- return res;
136
- }
137
- else {
138
- // tslint:disable-next-line: no-invalid-this
139
- return value.apply(this, props);
140
- }
141
- }
142
- };
143
- // bind app_ready hook event
144
- InitCacheStore();
145
- return descriptor;
146
- };
147
- }
148
- /**
149
- * Decorating the execution of this method will trigger a cache clear operation.
150
- * CacheStore server config defined in db.ts.
151
- *
152
- * @export
153
- * @param {string} cacheName cacheName cache name
154
- * @param {CacheEvictOpt} [opt] cache options
155
- * e.g:
156
- * {
157
- * params: ["id"],
158
- * delayedDoubleDeletion: true
159
- * }
160
- * Use the 'id' parameters of the method as cache subkeys,
161
- * and clear the cache after the method executed
162
- * @returns
163
- */
164
- function CacheEvict(cacheName, opt = {
165
- delayedDoubleDeletion: true,
166
- }) {
167
- return (target, methodName, descriptor) => {
168
- const componentType = IOCContainer.getType(target);
169
- if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
170
- throw Error("This decorator only used in the service、component class.");
171
- }
172
- const { value, configurable, enumerable } = descriptor;
173
- // 获取定义的参数位置
174
- opt = {
175
- ...{
176
- eventTime: "Before",
177
- }, ...opt
178
- };
179
- const paramIndexes = getParamIndex(opt.params);
180
- descriptor = {
181
- configurable,
182
- enumerable,
183
- writable: true,
184
- async value(...props) {
185
- let cacheFlag = true;
186
- const store = await GetCacheStore(this.app).catch(() => {
187
- cacheFlag = false;
188
- DefaultLogger.Error("Get cache store instance failed.");
189
- return null;
190
- });
191
- if (cacheFlag) {
192
- let key = cacheName;
193
- if (props && props.length > 0) {
194
- for (const item of paramIndexes) {
195
- if (props[item] !== undefined) {
196
- const value = Helper.toString(props[item]);
197
- key += `:${opt.params[item]}:${value}`;
198
- }
199
- }
200
- // 防止key超长
201
- if (key.length > longKey) {
202
- key = Helper.murmurHash(key);
203
- }
204
- }
205
- // tslint:disable-next-line: no-invalid-this
206
- const res = await value.apply(this, props);
207
- store.del(key).catch(() => null);
208
- if (opt.delayedDoubleDeletion) {
209
- asyncDelayedExecution(2000, () => {
210
- store.del(key).catch(() => null);
211
- });
212
- }
213
- return res;
214
- }
215
- else {
216
- // tslint:disable-next-line: no-invalid-this
217
- return value.apply(this, props);
218
- }
219
- }
220
- };
221
- // bind app_ready hook event
222
- InitCacheStore();
223
- return descriptor;
224
- };
225
- }
226
- /**
227
- * @description:
228
- * @param {string[]} params
229
- * @return {*}
230
- */
231
- function getParamIndex(params) {
232
- const res = [];
233
- for (let i = 0; i < params.length; i++) {
234
- if (params.includes(params[i])) {
235
- res.push(i);
236
- }
237
- }
238
- return res;
239
- }
240
- /**
241
- *
242
- * @param ms
243
- * @returns
244
- */
245
- function delay(ms) {
246
- return new Promise(resolve => setTimeout(resolve, ms));
247
- }
248
- /**
249
- * async delayed execution func
250
- * @param ms
251
- * @param fn
252
- * @returns
253
- */
254
- async function asyncDelayedExecution(ms, fn) {
255
- await delay(ms); // delay ms second
256
- return fn();
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 };