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