koatty_cacheable 1.4.3 → 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,239 +1,272 @@
1
1
  /*!
2
2
  * @Author: richen
3
- * @Date: 2024-01-17 22:08:01
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-01-17 21:54:53
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. Redis server config from db.ts.
63
- * The cache method returns a value to ensure that the next time the method is executed with the same parameters,
64
- * the results can be obtained directly from the cache without the need to execute the method again.
65
- *
66
- * @export
67
- * @param {string} cacheName cache name
68
- * @param {CacheAbleOpt} [opt] cache options
69
- * e.g:
70
- * {
71
- * params: ["id"],
72
- * timeout: 30
73
- * }
74
- * Use the 'id' parameters of the method as cache subkeys, the cache expiration time 30s
75
- * @returns {MethodDecorator}
76
- */
77
- function CacheAble(cacheName, opt = {
78
- params: [],
79
- timeout: 300,
80
- }) {
81
- return (target, methodName, descriptor) => {
82
- const componentType = IOCContainer.getType(target);
83
- if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
84
- throw Error("This decorator only used in the service、component class.");
85
- }
86
- const { value, configurable, enumerable } = descriptor;
87
- opt = {
88
- ...{
89
- params: [],
90
- timeout: 300,
91
- }, ...opt
92
- };
93
- // 获取定义的参数位置
94
- const paramIndexes = getParamIndex(opt.params);
95
- descriptor = {
96
- configurable,
97
- enumerable,
98
- writable: true,
99
- async value(...props) {
100
- let cacheFlag = true;
101
- const store = await GetCacheStore(this.app).catch(() => {
102
- cacheFlag = false;
103
- DefaultLogger.Error("Get cache store instance failed.");
104
- return null;
105
- });
106
- if (cacheFlag) {
107
- // tslint:disable-next-line: one-variable-per-declaration
108
- let key = cacheName;
109
- if (props && props.length > 0) {
110
- for (const item of paramIndexes) {
111
- if (props[item] !== undefined) {
112
- const value = Helper.toString(props[item]);
113
- key += `:${opt.params[item]}:${value}`;
114
- }
115
- }
116
- // 防止key超长
117
- if (key.length > longKey) {
118
- key = Helper.murmurHash(key);
119
- }
120
- }
121
- let res = await store.get(key).catch(() => null);
122
- if (!Helper.isEmpty(res)) {
123
- return JSON.parse(res);
124
- }
125
- // tslint:disable-next-line: no-invalid-this
126
- res = await value.apply(this, props);
127
- // prevent cache penetration
128
- if (Helper.isTrueEmpty(res)) {
129
- res = "";
130
- }
131
- // async set store
132
- store.set(key, JSON.stringify(res), opt.timeout).catch(() => null);
133
- return res;
134
- }
135
- else {
136
- // tslint:disable-next-line: no-invalid-this
137
- return value.apply(this, props);
138
- }
139
- }
140
- };
141
- // bind app_ready hook event
142
- InitCacheStore();
143
- return descriptor;
144
- };
145
- }
146
- /**
147
- * Decorating the execution of this method will trigger a cache clear operation. Redis server config from db.ts.
148
- *
149
- * @export
150
- * @param {string} cacheName cacheName cache name
151
- * @param {CacheEvictOpt} [opt] cache options
152
- * e.g:
153
- * {
154
- * params: ["id"],
155
- * eventTime: "Before"
156
- * }
157
- * Use the 'id' parameters of the method as cache subkeys, and clear the cache before the method executed
158
- * @returns
159
- */
160
- function CacheEvict(cacheName, opt = {
161
- eventTime: "Before",
162
- }) {
163
- return (target, methodName, descriptor) => {
164
- const componentType = IOCContainer.getType(target);
165
- if (componentType !== "SERVICE" && componentType !== "COMPONENT") {
166
- throw Error("This decorator only used in the service、component class.");
167
- }
168
- const { value, configurable, enumerable } = descriptor;
169
- // 获取定义的参数位置
170
- opt = {
171
- ...{
172
- eventTime: "Before",
173
- }, ...opt
174
- };
175
- const paramIndexes = getParamIndex(opt.params);
176
- descriptor = {
177
- configurable,
178
- enumerable,
179
- writable: true,
180
- async value(...props) {
181
- let cacheFlag = true;
182
- const store = await GetCacheStore(this.app).catch(() => {
183
- cacheFlag = false;
184
- DefaultLogger.Error("Get cache store instance failed.");
185
- return null;
186
- });
187
- if (cacheFlag) {
188
- let key = cacheName;
189
- if (props && props.length > 0) {
190
- for (const item of paramIndexes) {
191
- if (props[item] !== undefined) {
192
- const value = Helper.toString(props[item]);
193
- key += `:${opt.params[item]}:${value}`;
194
- }
195
- }
196
- // 防止key超长
197
- if (key.length > longKey) {
198
- key = Helper.murmurHash(key);
199
- }
200
- }
201
- if (opt.eventTime === "Before") {
202
- await store.del(key).catch(() => null);
203
- // tslint:disable-next-line: no-invalid-this
204
- return value.apply(this, props);
205
- }
206
- else {
207
- // tslint:disable-next-line: no-invalid-this
208
- const res = await value.apply(this, props);
209
- store.del(key).catch(() => null);
210
- return res;
211
- }
212
- }
213
- else {
214
- // tslint:disable-next-line: no-invalid-this
215
- return value.apply(this, props);
216
- }
217
- }
218
- };
219
- // bind app_ready hook event
220
- InitCacheStore();
221
- return descriptor;
222
- };
223
- }
224
- /**
225
- * @description:
226
- * @param {string[]} params
227
- * @return {*}
228
- */
229
- function getParamIndex(params) {
230
- const res = [];
231
- for (let i = 0; i < params.length; i++) {
232
- if (params.includes(params[i])) {
233
- res.push(i);
234
- }
235
- }
236
- return res;
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
+ };
237
270
  }
238
271
 
239
272
  export { CacheAble, CacheEvict, GetCacheStore };