lemon-core 3.1.0 → 3.1.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.
Files changed (101) hide show
  1. package/README.md +1 -0
  2. package/dist/controllers/dummy-controller.d.ts +1 -1
  3. package/dist/controllers/dummy-controller.js +2 -2
  4. package/dist/controllers/dummy-controller.js.map +1 -1
  5. package/dist/controllers/general-api-controller.d.ts +1 -1
  6. package/dist/cores/api/api-service.d.ts +239 -0
  7. package/dist/cores/api/api-service.js +674 -0
  8. package/dist/cores/api/api-service.js.map +1 -0
  9. package/dist/cores/api/index.d.ts +10 -0
  10. package/dist/cores/api/index.js +27 -0
  11. package/dist/cores/api/index.js.map +1 -0
  12. package/dist/cores/aws/aws-kms-service.d.ts +53 -2
  13. package/dist/cores/aws/aws-kms-service.js +104 -17
  14. package/dist/cores/aws/aws-kms-service.js.map +1 -1
  15. package/dist/cores/cache/cache-service.d.ts +440 -0
  16. package/dist/cores/cache/cache-service.js +967 -0
  17. package/dist/cores/cache/cache-service.js.map +1 -0
  18. package/dist/cores/cache/index.d.ts +10 -0
  19. package/dist/cores/cache/index.js +27 -0
  20. package/dist/cores/cache/index.js.map +1 -0
  21. package/dist/cores/cache-service.d.ts +54 -18
  22. package/dist/cores/cache-service.js +37 -26
  23. package/dist/cores/cache-service.js.map +1 -1
  24. package/dist/cores/core-types.d.ts +42 -12
  25. package/dist/cores/dynamo/dynamo-query-service.d.ts +52 -0
  26. package/dist/cores/dynamo/dynamo-query-service.js +127 -0
  27. package/dist/cores/dynamo/dynamo-query-service.js.map +1 -0
  28. package/dist/cores/dynamo/dynamo-scan-service.d.ts +70 -0
  29. package/dist/cores/dynamo/dynamo-scan-service.js +164 -0
  30. package/dist/cores/dynamo/dynamo-scan-service.js.map +1 -0
  31. package/dist/cores/dynamo/dynamo-service.d.ts +192 -0
  32. package/dist/cores/dynamo/dynamo-service.js +525 -0
  33. package/dist/cores/dynamo/dynamo-service.js.map +1 -0
  34. package/dist/cores/dynamo/index.d.ts +12 -0
  35. package/dist/cores/dynamo/index.js +29 -0
  36. package/dist/cores/dynamo/index.js.map +1 -0
  37. package/dist/cores/elastic/elastic6-query-service.d.ts +104 -0
  38. package/dist/cores/elastic/elastic6-query-service.js +510 -0
  39. package/dist/cores/elastic/elastic6-query-service.js.map +1 -0
  40. package/dist/cores/elastic/elastic6-service.d.ts +273 -0
  41. package/dist/cores/elastic/elastic6-service.js +903 -0
  42. package/dist/cores/elastic/elastic6-service.js.map +1 -0
  43. package/dist/cores/elastic/hangul-service.d.ts +102 -0
  44. package/dist/cores/elastic/hangul-service.js +205 -0
  45. package/dist/cores/elastic/hangul-service.js.map +1 -0
  46. package/dist/cores/elastic/index.d.ts +12 -0
  47. package/dist/cores/elastic/index.js +29 -0
  48. package/dist/cores/elastic/index.js.map +1 -0
  49. package/dist/cores/hangul-service.d.ts +17 -3
  50. package/dist/cores/hangul-service.js +17 -8
  51. package/dist/cores/hangul-service.js.map +1 -1
  52. package/dist/cores/index.d.ts +5 -11
  53. package/dist/cores/index.js +8 -13
  54. package/dist/cores/index.js.map +1 -1
  55. package/dist/cores/lambda/lambda-dynamo-stream-handler.d.ts +2 -2
  56. package/dist/cores/lambda/lambda-web-handler.d.ts +158 -8
  57. package/dist/cores/lambda/lambda-web-handler.js +283 -77
  58. package/dist/cores/lambda/lambda-web-handler.js.map +1 -1
  59. package/dist/cores/protocol/protocol-service.d.ts +4 -0
  60. package/dist/cores/protocol/protocol-service.js +12 -7
  61. package/dist/cores/protocol/protocol-service.js.map +1 -1
  62. package/dist/cores/storage/http-storage-service.d.ts +22 -0
  63. package/dist/cores/storage/http-storage-service.js +129 -0
  64. package/dist/cores/storage/http-storage-service.js.map +1 -0
  65. package/dist/cores/storage/index.d.ts +14 -0
  66. package/dist/cores/storage/index.js +31 -0
  67. package/dist/cores/storage/index.js.map +1 -0
  68. package/dist/cores/storage/model-manager.d.ts +93 -0
  69. package/dist/cores/storage/model-manager.js +192 -0
  70. package/dist/cores/storage/model-manager.js.map +1 -0
  71. package/dist/cores/storage/proxy-storage-service.d.ts +573 -0
  72. package/dist/cores/storage/proxy-storage-service.js +913 -0
  73. package/dist/cores/storage/proxy-storage-service.js.map +1 -0
  74. package/dist/cores/storage/redis-storage-service.d.ts +183 -0
  75. package/dist/cores/storage/redis-storage-service.js +391 -0
  76. package/dist/cores/storage/redis-storage-service.js.map +1 -0
  77. package/dist/cores/storage/storage-service.d.ts +169 -0
  78. package/dist/cores/storage/storage-service.js +374 -0
  79. package/dist/cores/storage/storage-service.js.map +1 -0
  80. package/dist/cores/storage-service.d.ts +1 -1
  81. package/dist/cores/storage-service.js +2 -2
  82. package/dist/cores/storage-service.js.map +1 -1
  83. package/dist/engine/utilities.d.ts +4 -3
  84. package/dist/engine/utilities.js +6 -6
  85. package/dist/engine/utilities.js.map +1 -1
  86. package/dist/environ.d.ts +2 -2
  87. package/dist/environ.js +7 -4
  88. package/dist/environ.js.map +1 -1
  89. package/dist/extended/abstract-service.d.ts +533 -0
  90. package/dist/extended/abstract-service.js +915 -0
  91. package/dist/extended/abstract-service.js.map +1 -0
  92. package/dist/extended/index.d.ts +10 -0
  93. package/dist/extended/index.js +27 -0
  94. package/dist/extended/index.js.map +1 -0
  95. package/dist/helpers/helpers.d.ts +7 -0
  96. package/dist/helpers/helpers.js +7 -0
  97. package/dist/helpers/helpers.js.map +1 -1
  98. package/dist/index.d.ts +1 -0
  99. package/dist/index.js +3 -1
  100. package/dist/index.js.map +1 -1
  101. package/package.json +6 -4
@@ -0,0 +1,967 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.fromTTL = exports.toTTL = exports.sleep = exports.DummyCacheService = exports.CacheService = void 0;
16
+ /**
17
+ * `cache-services.ts`
18
+ * - common service for remote cache
19
+ *
20
+ * @author Tim Hong <tim@lemoncloud.io>
21
+ * @date 2020-12-02 initial version
22
+ * @author Steve <steve@lemoncloud.io>
23
+ * @date 2022-04-01 optimized for `AbstractProxy`
24
+ *
25
+ * @copyright (C) lemoncloud.io 2020 - All Rights Reserved.
26
+ */
27
+ const util_1 = require("util");
28
+ const node_cache_1 = __importDefault(require("node-cache"));
29
+ const memcached_1 = __importDefault(require("memcached"));
30
+ const ioredis_1 = __importDefault(require("ioredis"));
31
+ const engine_1 = require("../../engine");
32
+ const NS = engine_1.$U.NS('CCHS', 'green'); // NAMESPACE TO BE PRINTED.
33
+ /**
34
+ * class `CacheService`
35
+ * - common service to provide cache
36
+ */
37
+ class CacheService {
38
+ /**
39
+ * Protected constructor -> use CacheService.create()
40
+ * WARN! - do not create directly.˜
41
+ *
42
+ * @param backend cache backend object
43
+ * @param params params to create service.
44
+ * @protected
45
+ */
46
+ constructor(backend, params) {
47
+ if (!backend)
48
+ throw new Error(`@backend (cache-backend) is required!`);
49
+ const ns = (params === null || params === void 0 ? void 0 : params.ns) || '';
50
+ (0, engine_1._inf)(NS, `! cache-service instantiated with [${backend.name}] backend. [ns=${ns}]`);
51
+ this.backend = backend;
52
+ this.ns = ns;
53
+ this.options = params.options;
54
+ this.maker = params === null || params === void 0 ? void 0 : params.maker;
55
+ }
56
+ /**
57
+ * Factory method
58
+ *
59
+ * @param options (optional) cache options
60
+ * @param maker (optional) custome cache-pkey generator.
61
+ * @static
62
+ */
63
+ static create(options, maker) {
64
+ const type = (options === null || options === void 0 ? void 0 : options.type) || 'redis';
65
+ const endpoint = (options === null || options === void 0 ? void 0 : options.endpoint) || engine_1.$U.env(CacheService.ENV_CACHE_ENDPOINT);
66
+ const ns = (options === null || options === void 0 ? void 0 : options.ns) || '';
67
+ const defTimeout = engine_1.$U.N(options === null || options === void 0 ? void 0 : options.defTimeout, engine_1.$U.N(engine_1.$U.env(CacheService.ENV_CACHE_DEFAULT_TIMEOUT), CacheService.DEF_CACHE_DEFAULT_TIMEOUT));
68
+ options = Object.assign(Object.assign({}, options), { type, endpoint, ns, defTimeout });
69
+ (0, engine_1._log)(NS, `constructing [${type}] cache ...`);
70
+ (0, engine_1._log)(NS, ` > endpoint =`, endpoint);
71
+ (0, engine_1._log)(NS, ` > ns =`, ns);
72
+ (0, engine_1._log)(NS, ` > defTimeout =`, defTimeout);
73
+ let backend;
74
+ switch (type) {
75
+ case 'memcached':
76
+ backend = new MemcachedBackend(endpoint, defTimeout);
77
+ break;
78
+ case 'redis':
79
+ backend = new RedisBackend(endpoint, defTimeout);
80
+ break;
81
+ default:
82
+ throw new Error(`@type [${type}] is invalid - CacheService.create()`);
83
+ }
84
+ return new CacheService(backend, { ns, options, maker });
85
+ }
86
+ /**
87
+ * Say hello
88
+ */
89
+ hello() {
90
+ return `cache-service:${this.backend.name}:${this.ns}`;
91
+ }
92
+ /**
93
+ * for convient, make another typed service.
94
+ * - it add `type` into key automatically.
95
+ *
96
+ * @param type model-type like 'test'
97
+ * @param delimiter (optional) delim bewteen type and key (default ':')
98
+ * @returns the typed CacheService
99
+ */
100
+ cloneByType(type, delimiter = ':') {
101
+ const { backend, ns, options } = this;
102
+ const maker = (ns, delim, key) => this.asNamespacedKey(`${type}${delimiter}${key}`);
103
+ return new CacheService(backend, { ns, options, maker });
104
+ }
105
+ /**
106
+ * Close backend and connection
107
+ */
108
+ close() {
109
+ return __awaiter(this, void 0, void 0, function* () {
110
+ yield this.backend.close();
111
+ });
112
+ }
113
+ /**
114
+ * Check whether the key is cached
115
+ *
116
+ * @return true if the key is cached
117
+ */
118
+ exists(key) {
119
+ return __awaiter(this, void 0, void 0, function* () {
120
+ const namespacedKey = this.asNamespacedKey(key);
121
+ const ret = yield this.backend.has(namespacedKey);
122
+ (0, engine_1._log)(NS, `.exists ${namespacedKey} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
123
+ return ret;
124
+ });
125
+ }
126
+ /**
127
+ * List all keys
128
+ *
129
+ * @return list of keys
130
+ */
131
+ keys() {
132
+ return __awaiter(this, void 0, void 0, function* () {
133
+ const namespacedKeys = yield this.backend.keys();
134
+ const ret = namespacedKeys.reduce((keys, namespacedKey) => {
135
+ const [ns, key] = namespacedKey.split(CacheService.NAMESPACE_DELIMITER);
136
+ if (ns === this.ns)
137
+ keys.push(key);
138
+ return keys;
139
+ }, []);
140
+ (0, engine_1._log)(NS, `.keys / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
141
+ return ret;
142
+ });
143
+ }
144
+ /**
145
+ * Store a key
146
+ *
147
+ * @param key
148
+ * @param val
149
+ * @param timeout (optional) TTL in seconds or Timeout object
150
+ * @return true on success
151
+ */
152
+ set(key, val, timeout) {
153
+ return __awaiter(this, void 0, void 0, function* () {
154
+ if (!key)
155
+ throw new Error(`@key (CacheKey) is required.`);
156
+ if (val === undefined)
157
+ throw new Error(`@val (CacheValue) cannot be undefined.`);
158
+ const namespacedKey = this.asNamespacedKey(key);
159
+ const ttl = timeout && toTTL(timeout);
160
+ const ret = yield this.backend.set(namespacedKey, val, ttl);
161
+ (0, engine_1._log)(NS, `.set ${namespacedKey} ${val} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
162
+ return ret;
163
+ });
164
+ }
165
+ /**
166
+ * Store multiple keys
167
+ *
168
+ * @param entries
169
+ * @return true on success
170
+ */
171
+ setMulti(entries) {
172
+ return __awaiter(this, void 0, void 0, function* () {
173
+ const param = entries.map(({ key, val, timeout }, idx) => {
174
+ if (!key)
175
+ throw new Error(`.key (CacheKey) is required (at @entries[${idx}]).`);
176
+ if (val === undefined)
177
+ throw new Error(`.val (CacheValue) cannot be undefined (at @entries[${idx}]).`);
178
+ return {
179
+ key: this.asNamespacedKey(key),
180
+ val,
181
+ ttl: timeout && toTTL(timeout),
182
+ };
183
+ });
184
+ const ret = yield this.backend.mset(param);
185
+ (0, engine_1._log)(NS, `.setMulti ${entries.map(entry => entry.key)} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
186
+ return ret;
187
+ });
188
+ }
189
+ /**
190
+ * Retrieve a key
191
+ *
192
+ * @param key
193
+ */
194
+ get(key) {
195
+ return __awaiter(this, void 0, void 0, function* () {
196
+ if (!key)
197
+ throw new Error(`@key (CacheKey) is required.`);
198
+ const namespacedKey = this.asNamespacedKey(key);
199
+ const ret = yield this.backend.get(namespacedKey);
200
+ (0, engine_1._log)(NS, `.get ${namespacedKey} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
201
+ return ret;
202
+ });
203
+ }
204
+ /**
205
+ * Get multiple keys
206
+ *
207
+ * @param keys
208
+ */
209
+ getMulti(keys) {
210
+ return __awaiter(this, void 0, void 0, function* () {
211
+ const namespacedKeys = keys.map((key, idx) => {
212
+ if (!key)
213
+ throw new Error(`@key (CacheKey) is required (at @keys[${idx}]).`);
214
+ return this.asNamespacedKey(key);
215
+ });
216
+ const map = yield this.backend.mget(namespacedKeys);
217
+ // Remove namespace prefix from keys
218
+ const ret = Object.entries(map).reduce((newMap, [namespacedKey, val]) => {
219
+ const key = namespacedKey.split(CacheService.NAMESPACE_DELIMITER)[1];
220
+ newMap[key] = val;
221
+ return newMap;
222
+ }, {});
223
+ (0, engine_1._log)(NS, `.getMulti ${namespacedKeys} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
224
+ return ret;
225
+ });
226
+ }
227
+ /**
228
+ * Increment the integer value of a key
229
+ *
230
+ * @param key
231
+ * @param inc number to increment
232
+ */
233
+ increment(key, inc) {
234
+ return __awaiter(this, void 0, void 0, function* () {
235
+ if (!key)
236
+ throw new Error(`@key (CacheKey) is required.`);
237
+ if (inc === undefined)
238
+ throw new Error(`@inc (number) cannot be undefined.`);
239
+ const namespacedKey = this.asNamespacedKey(key);
240
+ const ret = yield this.backend.incr(namespacedKey, inc);
241
+ (0, engine_1._log)(NS, `.increment ${namespacedKey} ${inc} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
242
+ return ret;
243
+ });
244
+ }
245
+ /**
246
+ * same as increment()
247
+ */
248
+ inc(key, inc) {
249
+ return this.increment(key, inc);
250
+ }
251
+ /**
252
+ * Set the value of a key and return its old value
253
+ */
254
+ getAndSet(key, val) {
255
+ return __awaiter(this, void 0, void 0, function* () {
256
+ if (!key)
257
+ throw new Error(`@key (CacheKey) is required.`);
258
+ if (val === undefined)
259
+ throw new Error(`@val (CacheValue) cannot be undefined.`);
260
+ const namespacedKey = this.asNamespacedKey(key);
261
+ let ret;
262
+ if (this.backend.getset) {
263
+ ret = yield this.backend.getset(namespacedKey, val);
264
+ }
265
+ else {
266
+ ret = yield this.backend.get(namespacedKey);
267
+ // Best effort to keep remaining TTL
268
+ let ttl = yield this.backend.ttl(namespacedKey);
269
+ if (ttl !== undefined) {
270
+ ttl = Math.ceil(ttl / 1000);
271
+ }
272
+ if (!(yield this.backend.set(namespacedKey, val, ttl)))
273
+ throw new Error(`getAndSet() failed`);
274
+ }
275
+ (0, engine_1._log)(NS, `.getAndSet ${namespacedKey} ${val} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
276
+ return ret;
277
+ });
278
+ }
279
+ /**
280
+ * Get and delete the key
281
+ *
282
+ * @param key
283
+ */
284
+ getAndDelete(key) {
285
+ return __awaiter(this, void 0, void 0, function* () {
286
+ if (!key)
287
+ throw new Error(`@key (CacheKey) is required.`);
288
+ const namespacedKey = this.asNamespacedKey(key);
289
+ let ret;
290
+ if (this.backend.pop) {
291
+ ret = yield this.backend.pop(namespacedKey);
292
+ }
293
+ else {
294
+ ret = yield this.backend.get(namespacedKey);
295
+ yield this.backend.del(namespacedKey);
296
+ }
297
+ (0, engine_1._log)(NS, `.getAndDelete ${namespacedKey} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
298
+ return ret;
299
+ });
300
+ }
301
+ /**
302
+ * Delete a key
303
+ *
304
+ * @param key
305
+ * @return true on success
306
+ */
307
+ delete(key) {
308
+ return __awaiter(this, void 0, void 0, function* () {
309
+ if (!key)
310
+ throw new Error(`@key (CacheKey) is required.`);
311
+ const namespacedKey = this.asNamespacedKey(key);
312
+ const ret = yield this.backend.del(namespacedKey);
313
+ (0, engine_1._log)(NS, `.delete ${namespacedKey} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
314
+ return ret;
315
+ });
316
+ }
317
+ /**
318
+ * Delete multiple keys
319
+ *
320
+ * @param keys
321
+ * @return number of deleted entries
322
+ */
323
+ deleteMulti(keys) {
324
+ return __awaiter(this, void 0, void 0, function* () {
325
+ const namespacedKeys = keys.map((key, idx) => {
326
+ if (!key)
327
+ throw new Error(`@key (CacheKey) is required (at @keys[${idx}]).`);
328
+ return this.asNamespacedKey(key);
329
+ });
330
+ const promises = namespacedKeys.map(namespacedKey => this.backend.del(namespacedKey));
331
+ const ret = yield Promise.all(promises);
332
+ (0, engine_1._log)(NS, `.deleteMulti ${namespacedKeys} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
333
+ return ret;
334
+ });
335
+ }
336
+ /**
337
+ * Set or update the timeout of a key
338
+ *
339
+ * @param key
340
+ * @param timeout TTL in seconds or Timeout object
341
+ * @return true on success
342
+ */
343
+ setTimeout(key, timeout) {
344
+ return __awaiter(this, void 0, void 0, function* () {
345
+ if (!key)
346
+ throw new Error(`@key (CacheKey) is required.`);
347
+ const namespacedKey = this.asNamespacedKey(key);
348
+ const ret = yield this.backend.expire(namespacedKey, toTTL(timeout));
349
+ (0, engine_1._log)(NS, `.setTimeout ${namespacedKey} ${timeout} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
350
+ return ret;
351
+ });
352
+ }
353
+ /**
354
+ * Get remaining time to live in milliseconds
355
+ *
356
+ * @return
357
+ * - number of milliseconds to expire
358
+ * - undefined if the key does not exist
359
+ * - 0 if the key has no timeout
360
+ */
361
+ getTimeout(key) {
362
+ return __awaiter(this, void 0, void 0, function* () {
363
+ if (!key)
364
+ throw new Error(`@key (CacheKey) is required.`);
365
+ const namespacedKey = this.asNamespacedKey(key);
366
+ const ret = yield this.backend.ttl(namespacedKey);
367
+ (0, engine_1._log)(NS, `.getTimeout ${namespacedKey} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
368
+ return ret;
369
+ });
370
+ }
371
+ /**
372
+ * Remove the timeout from a key
373
+ *
374
+ * @param key
375
+ */
376
+ removeTimeout(key) {
377
+ return __awaiter(this, void 0, void 0, function* () {
378
+ if (!key)
379
+ throw new Error(`@key (CacheKey) is required.`);
380
+ const namespacedKey = this.asNamespacedKey(key);
381
+ const ret = yield this.backend.expire(namespacedKey, 0);
382
+ (0, engine_1._log)(NS, `.removeTimeout ${namespacedKey} / ret =`, typeof ret === 'string' ? ret : engine_1.$U.json(ret));
383
+ return ret;
384
+ });
385
+ }
386
+ /**
387
+ * Get namespace prefixed cache key
388
+ *
389
+ * @param key
390
+ * @protected
391
+ */
392
+ asNamespacedKey(key) {
393
+ const [ns, delim] = [this.ns, CacheService.NAMESPACE_DELIMITER];
394
+ if (this.maker)
395
+ return this.maker(ns, delim, key);
396
+ return `${ns}${delim}${key}`;
397
+ }
398
+ }
399
+ exports.CacheService = CacheService;
400
+ /**
401
+ * Environment variable name for cache server endpoint
402
+ * @static
403
+ */
404
+ CacheService.ENV_CACHE_ENDPOINT = 'CACHE_ENDPOINT';
405
+ /**
406
+ * Environment variable name for default cache timeout
407
+ * @static
408
+ */
409
+ CacheService.ENV_CACHE_DEFAULT_TIMEOUT = 'CACHE_DEFAULT_TIMEOUT';
410
+ /**
411
+ * Default cache timeout
412
+ * @static
413
+ */
414
+ CacheService.DEF_CACHE_DEFAULT_TIMEOUT = 24 * 60 * 60; // 1-day
415
+ /**
416
+ * Namespace delimiter
417
+ * @private
418
+ * @static
419
+ */
420
+ CacheService.NAMESPACE_DELIMITER = '::';
421
+ /**
422
+ * class `DummyCacheService`: use 'node-cache' library
423
+ */
424
+ class DummyCacheService extends CacheService {
425
+ /**
426
+ * Factory method
427
+ *
428
+ * @param options (optional) cache options
429
+ * @static
430
+ */
431
+ static create(options) {
432
+ const ns = (options === null || options === void 0 ? void 0 : options.ns) || '';
433
+ const defTimeout = engine_1.$U.N(options === null || options === void 0 ? void 0 : options.defTimeout, engine_1.$U.N(engine_1.$U.env(CacheService.ENV_CACHE_DEFAULT_TIMEOUT), CacheService.DEF_CACHE_DEFAULT_TIMEOUT));
434
+ options = Object.assign(Object.assign({}, options), { ns, defTimeout });
435
+ (0, engine_1._log)(NS, `constructing dummy cache ...`);
436
+ // NOTE: Use singleton backend instance
437
+ // because node-cache is volatile and client instance does not share keys with other instance
438
+ if (!DummyCacheService.backend)
439
+ DummyCacheService.backend = new NodeCacheBackend(defTimeout);
440
+ return new DummyCacheService(DummyCacheService.backend, { ns, options });
441
+ }
442
+ /**
443
+ * Say hello
444
+ */
445
+ hello() {
446
+ return `dummy-${super.hello()}`;
447
+ }
448
+ }
449
+ exports.DummyCacheService = DummyCacheService;
450
+ /**
451
+ * function `sleep`
452
+ * @param ms duration in milliseconds
453
+ */
454
+ function sleep(ms) {
455
+ return __awaiter(this, void 0, void 0, function* () {
456
+ return new Promise(resolve => setTimeout(resolve, ms));
457
+ });
458
+ }
459
+ exports.sleep = sleep;
460
+ /**
461
+ * Get TTL from timeout
462
+ * @param timeout timeout in seconds or Timeout object
463
+ * @return remaining time to live in seconds
464
+ */
465
+ function toTTL(timeout) {
466
+ switch (typeof timeout) {
467
+ case 'number':
468
+ return timeout;
469
+ case 'object':
470
+ if (!timeout)
471
+ return 0;
472
+ const { expireIn, expireAt } = timeout;
473
+ if (typeof expireIn === 'number')
474
+ return expireIn;
475
+ if (typeof expireAt === 'number') {
476
+ const msTTL = timeout.expireAt - Date.now();
477
+ return Math.ceil(msTTL / 1000);
478
+ }
479
+ break;
480
+ }
481
+ throw new Error(`@timeout (number | Timeout) is invalid.`);
482
+ }
483
+ exports.toTTL = toTTL;
484
+ /**
485
+ * Get timestamp of expiration from TTL
486
+ * @param ttl remaining time to live in seconds
487
+ * @return timestamp in milliseconds since epoch
488
+ */
489
+ function fromTTL(ttl) {
490
+ return ttl > 0 ? Date.now() + ttl * 1000 : 0;
491
+ }
492
+ exports.fromTTL = fromTTL;
493
+ /** ********************************************************************************************************************
494
+ * Internal Classes
495
+ ** ********************************************************************************************************************/
496
+ /**
497
+ * class `NodeCacheBackend`: use 'node-cache' library
498
+ * @internal
499
+ */
500
+ class NodeCacheBackend {
501
+ /**
502
+ * Public constructor
503
+ */
504
+ constructor(defTTL = 0) {
505
+ /**
506
+ * backend type
507
+ */
508
+ this.name = 'node-cache';
509
+ this.cache = new node_cache_1.default({ stdTTL: defTTL });
510
+ }
511
+ /**
512
+ * CacheBackend.set implementation
513
+ */
514
+ set(key, val, ttl) {
515
+ return __awaiter(this, void 0, void 0, function* () {
516
+ return this.cache.set(key, val, ttl);
517
+ });
518
+ }
519
+ /**
520
+ * CacheBackend.get implementation
521
+ */
522
+ get(key) {
523
+ return __awaiter(this, void 0, void 0, function* () {
524
+ return this.cache.get(key);
525
+ });
526
+ }
527
+ /**
528
+ * CacheBackend.mset implementation
529
+ */
530
+ mset(entries) {
531
+ return __awaiter(this, void 0, void 0, function* () {
532
+ return this.cache.mset(entries);
533
+ });
534
+ }
535
+ /**
536
+ * CacheBackend.mget implementation
537
+ */
538
+ mget(keys) {
539
+ return __awaiter(this, void 0, void 0, function* () {
540
+ return this.cache.mget(keys);
541
+ });
542
+ }
543
+ /**
544
+ * CacheBackend.pop implementation
545
+ */
546
+ pop(key) {
547
+ return __awaiter(this, void 0, void 0, function* () {
548
+ return this.cache.take(key);
549
+ });
550
+ }
551
+ /**
552
+ * CacheBackend.incr implementation
553
+ */
554
+ incr(key, increment) {
555
+ return __awaiter(this, void 0, void 0, function* () {
556
+ const org = this.cache.get(key);
557
+ if (typeof org !== 'number')
558
+ throw new Error(`@key [${key}] does not hold a number value.`);
559
+ const newVal = org + increment;
560
+ this.cache.set(key, newVal);
561
+ return newVal;
562
+ });
563
+ }
564
+ /**
565
+ * CacheBackend.keys implementation
566
+ */
567
+ keys() {
568
+ return __awaiter(this, void 0, void 0, function* () {
569
+ return this.cache.keys();
570
+ });
571
+ }
572
+ /**
573
+ * CacheBackend.has implementation
574
+ */
575
+ has(key) {
576
+ return __awaiter(this, void 0, void 0, function* () {
577
+ return this.cache.has(key);
578
+ });
579
+ }
580
+ /**
581
+ * CacheBackend.del implementation
582
+ */
583
+ del(key) {
584
+ return __awaiter(this, void 0, void 0, function* () {
585
+ return this.cache.del(key) === 1;
586
+ });
587
+ }
588
+ /**
589
+ * CacheBackend.expire implementation
590
+ */
591
+ expire(key, ttl) {
592
+ return __awaiter(this, void 0, void 0, function* () {
593
+ return this.cache.ttl(key, ttl);
594
+ });
595
+ }
596
+ /**
597
+ * CacheBackend.ttl implementation
598
+ */
599
+ ttl(key) {
600
+ return __awaiter(this, void 0, void 0, function* () {
601
+ const ts = this.cache.getTtl(key); // Timestamp in milliseconds
602
+ return ts && ts - Date.now();
603
+ });
604
+ }
605
+ /**
606
+ * CacheBackend.close implementation
607
+ */
608
+ close() {
609
+ return __awaiter(this, void 0, void 0, function* () {
610
+ this.cache.close();
611
+ });
612
+ }
613
+ }
614
+ /**
615
+ * class `MemcachedBackend`
616
+ * @internal
617
+ */
618
+ class MemcachedBackend {
619
+ /**
620
+ * Public constructor
621
+ */
622
+ constructor(endpoint, defTTL = 0) {
623
+ /**
624
+ * backend type
625
+ */
626
+ this.name = 'memcached';
627
+ const memcached = new memcached_1.default(endpoint || 'localhost:11211');
628
+ // Build promisified API map
629
+ this.api = {
630
+ get: (0, util_1.promisify)(memcached.get.bind(memcached)),
631
+ gets: (0, util_1.promisify)(memcached.gets.bind(memcached)),
632
+ getMulti: (0, util_1.promisify)(memcached.getMulti.bind(memcached)),
633
+ set: (0, util_1.promisify)(memcached.set.bind(memcached)),
634
+ cas: (0, util_1.promisify)(memcached.cas.bind(memcached)),
635
+ del: (0, util_1.promisify)(memcached.del.bind(memcached)),
636
+ items: (0, util_1.promisify)(memcached.items.bind(memcached)),
637
+ cachedump: (server, slabid, number) => {
638
+ return new Promise((resolve, reject) => {
639
+ memcached.cachedump(server, slabid, number, (err, cachedump) => {
640
+ if (err)
641
+ return reject(err);
642
+ if (!cachedump)
643
+ return resolve([]);
644
+ // Deep-copy를 안하면 데이터가 없어지는 이슈가 있음
645
+ resolve(Array.isArray(cachedump) ? [...cachedump] : [cachedump]);
646
+ });
647
+ });
648
+ },
649
+ end: memcached.end.bind(memcached),
650
+ };
651
+ // default TTL
652
+ this.defTTL = defTTL;
653
+ }
654
+ /**
655
+ * CacheBackend.set implementation
656
+ */
657
+ set(key, val, ttl = this.defTTL) {
658
+ return __awaiter(this, void 0, void 0, function* () {
659
+ const entry = { val, exp: fromTTL(ttl) };
660
+ (0, engine_1._log)(NS, `[${this.name}-backend] storing to key [${key}] =`, engine_1.$U.json(entry));
661
+ return yield this.api.set(key, entry, ttl);
662
+ });
663
+ }
664
+ /**
665
+ * CacheBackend.get implementation
666
+ */
667
+ get(key) {
668
+ return __awaiter(this, void 0, void 0, function* () {
669
+ const entry = yield this.api.get(key);
670
+ (0, engine_1._log)(NS, `[${this.name}-backend] entry fetched =`, engine_1.$U.json(entry));
671
+ return entry && entry.val;
672
+ });
673
+ }
674
+ /**
675
+ * CacheBackend.mset implementation
676
+ */
677
+ mset(entries) {
678
+ return __awaiter(this, void 0, void 0, function* () {
679
+ (0, engine_1._log)(NS, `[${this.name}-backend] storing multiple keys ...`);
680
+ const promises = entries.map(({ key, val, ttl = this.defTTL }, idx) => {
681
+ const entry = { val, exp: fromTTL(ttl) };
682
+ (0, engine_1._log)(NS, ` ${idx}) key [${key}] =`, engine_1.$U.json(entry));
683
+ return this.api.set(key, entry, ttl);
684
+ });
685
+ const results = yield Promise.all(promises);
686
+ return results.every(result => result === true);
687
+ });
688
+ }
689
+ /**
690
+ * CacheBackend.mget implementation
691
+ */
692
+ mget(keys) {
693
+ return __awaiter(this, void 0, void 0, function* () {
694
+ const map = yield this.api.getMulti(keys);
695
+ (0, engine_1._log)(NS, `[${this.name}-backend] entry map fetched =`, engine_1.$U.json(map));
696
+ Object.keys(map).forEach(key => {
697
+ const entry = map[key];
698
+ map[key] = entry.val;
699
+ });
700
+ return map;
701
+ });
702
+ }
703
+ /**
704
+ * CacheBackend.incr implementation
705
+ */
706
+ incr(key, increment) {
707
+ return __awaiter(this, void 0, void 0, function* () {
708
+ // NOTE:
709
+ // Memcached는 음수에 대한 incr/decr를 지원하지 않으며 0 미만으로 decr 되지 않는다.
710
+ // 이런 이유로 sets & cas 조합을 이용해 직접 구현함
711
+ (0, engine_1._log)(NS, `[${this.name}-backend] incrementing (${increment}) to key [${key}] ...`);
712
+ // Use get/check-and-save + retry strategy for consistency
713
+ for (let retry = 0; retry < 5; yield sleep(10), retry++) {
714
+ const result = yield this.api.gets(key); // Get entry w/ CAS id
715
+ if (result === undefined) {
716
+ // Initialize to increment value if the key does not exist
717
+ if (!(yield this.set(key, increment, 0)))
718
+ break;
719
+ return increment;
720
+ }
721
+ else {
722
+ const { [key]: oldEntry, cas } = result;
723
+ if (typeof oldEntry.val !== 'number')
724
+ throw new Error(`.key [${key}] has non-numeric value.`);
725
+ // Preserve remaining lifetime w/ best effort strategy, not accurate
726
+ const now = Date.now();
727
+ const ttl = oldEntry.exp && Math.round((oldEntry.exp - now) / 1000);
728
+ const entry = {
729
+ val: oldEntry.val + increment,
730
+ exp: ttl && now + ttl * 1000,
731
+ };
732
+ if (yield this.api.cas(key, entry, cas, ttl))
733
+ return entry.val;
734
+ }
735
+ }
736
+ throw new Error(`[memcached] failed to increment key [${key}].`);
737
+ });
738
+ }
739
+ /**
740
+ * CacheBackend.keys implementation
741
+ */
742
+ keys() {
743
+ return __awaiter(this, void 0, void 0, function* () {
744
+ // NOTE:
745
+ // memcached는 원래 keys 기능을 지원하지 않으며
746
+ // 아래와 같이 cachedump를 사용하여 가능하지만 set한 key가 dump 될 때 까지 상당한 시간이 소요되는 것으로 보인다.
747
+ // 따라서 이 operation의 결과를 신뢰하지 않도록 한다.
748
+ const item = (yield this.api.items())[0];
749
+ if (!item || Object.keys(item).length === 0)
750
+ return [];
751
+ const [server, slabid] = [item.server, Number(Object.keys(item)[0])];
752
+ const number = item[slabid].number;
753
+ const cachedump = yield this.api.cachedump(server, slabid, number);
754
+ return cachedump.map(({ key }) => key);
755
+ });
756
+ }
757
+ /**
758
+ * CacheBackend.has implementation
759
+ */
760
+ has(key) {
761
+ return __awaiter(this, void 0, void 0, function* () {
762
+ return (yield this.api.get(key)) !== undefined;
763
+ });
764
+ }
765
+ /**
766
+ * CacheBackend.del implementation
767
+ */
768
+ del(key) {
769
+ return __awaiter(this, void 0, void 0, function* () {
770
+ return yield this.api.del(key);
771
+ });
772
+ }
773
+ /**
774
+ * CacheBackend.expire implementation
775
+ */
776
+ expire(key, ttl) {
777
+ return __awaiter(this, void 0, void 0, function* () {
778
+ let saved = false;
779
+ for (let retry = 0; !saved && retry < 5; yield sleep(10), retry++) {
780
+ const result = yield this.api.gets(key); // Get entry w/ CAS id
781
+ if (result === undefined)
782
+ break; // If key does not exist or already expired
783
+ // Refresh timeout
784
+ const { [key]: oldEntry, cas } = result;
785
+ const newEntry = {
786
+ val: oldEntry.val,
787
+ exp: ttl && Date.now() + ttl * 1000,
788
+ };
789
+ saved = yield this.api.cas(key, newEntry, cas, ttl);
790
+ }
791
+ return saved;
792
+ });
793
+ }
794
+ /**
795
+ * CacheBackend.ttl implementation
796
+ */
797
+ ttl(key) {
798
+ return __awaiter(this, void 0, void 0, function* () {
799
+ const entry = yield this.api.get(key); // undefined if key does not exist
800
+ return (entry === null || entry === void 0 ? void 0 : entry.exp) && entry.exp - Date.now();
801
+ });
802
+ }
803
+ /**
804
+ * CacheBackend.close implementation
805
+ */
806
+ close() {
807
+ return __awaiter(this, void 0, void 0, function* () {
808
+ this.api.end();
809
+ });
810
+ }
811
+ }
812
+ /**
813
+ * class `RedisBackend`
814
+ * @internal
815
+ */
816
+ class RedisBackend {
817
+ /**
818
+ * Public constructor
819
+ */
820
+ constructor(endpoint, defTTL = 0) {
821
+ /**
822
+ * backend type
823
+ */
824
+ this.name = 'redis';
825
+ this.redis = new ioredis_1.default(endpoint || 'localhost:6379');
826
+ this.defTTL = defTTL;
827
+ }
828
+ /**
829
+ * CacheBackend.set implementation
830
+ */
831
+ set(key, val, ttl = this.defTTL) {
832
+ return __awaiter(this, void 0, void 0, function* () {
833
+ const data = JSON.stringify(val); // Serialize
834
+ ttl > 0 ? yield this.redis.set(key, data, 'EX', ttl) : yield this.redis.set(key, data);
835
+ return true; // 'set' command always return OK
836
+ });
837
+ }
838
+ /**
839
+ * CacheBackend.get implementation
840
+ */
841
+ get(key) {
842
+ return __awaiter(this, void 0, void 0, function* () {
843
+ const data = yield this.redis.get(key);
844
+ if (data !== null)
845
+ return JSON.parse(data); // Deserialize
846
+ });
847
+ }
848
+ /**
849
+ * CacheBackend.mset implementation
850
+ */
851
+ mset(entries) {
852
+ return __awaiter(this, void 0, void 0, function* () {
853
+ // Create transaction pipeline
854
+ // -> MSET command를 사용할 수도 있으나 ttl 지정이 불가능하여 pipeline으로 구현함
855
+ const pipeline = entries.reduce((pipeline, { key, val, ttl = this.defTTL }) => {
856
+ const data = JSON.stringify(val); // Serialize
857
+ return ttl > 0 ? pipeline.set(key, data, 'EX', ttl) : pipeline.set(key, data);
858
+ }, this.redis.multi());
859
+ // Execute transaction
860
+ yield pipeline.exec(); // Always OK
861
+ return true;
862
+ });
863
+ }
864
+ /**
865
+ * CacheBackend.mget implementation
866
+ */
867
+ mget(keys) {
868
+ return __awaiter(this, void 0, void 0, function* () {
869
+ const list = yield this.redis.mget(keys);
870
+ // Deserialize and map array into object
871
+ return list.reduce((map, data, idx) => {
872
+ if (data !== null) {
873
+ const key = keys[idx];
874
+ map[key] = JSON.parse(data); // Deserialize
875
+ }
876
+ return map;
877
+ }, {});
878
+ });
879
+ }
880
+ /**
881
+ * CacheBackend.getset implementation
882
+ */
883
+ getset(key, val) {
884
+ return __awaiter(this, void 0, void 0, function* () {
885
+ const newData = JSON.stringify(val); // Serialize
886
+ const oldData = yield this.redis.getset(key, newData);
887
+ if (oldData !== null)
888
+ return JSON.parse(oldData); // Deserialize
889
+ });
890
+ }
891
+ /**
892
+ * CacheBackend.pop implementation
893
+ */
894
+ pop(key) {
895
+ return __awaiter(this, void 0, void 0, function* () {
896
+ const [[err, data]] = yield this.redis
897
+ .multi()
898
+ .get(key) // read
899
+ .del(key) // and delete
900
+ .exec();
901
+ if (!err && data !== null)
902
+ return JSON.parse(data);
903
+ });
904
+ }
905
+ /**
906
+ * CacheBackend.incr implementation
907
+ */
908
+ incr(key, increment) {
909
+ return __awaiter(this, void 0, void 0, function* () {
910
+ return yield this.redis.incrbyfloat(key, increment); // Support both integer and floating point
911
+ });
912
+ }
913
+ /**
914
+ * CacheBackend.keys implementation
915
+ */
916
+ keys() {
917
+ return __awaiter(this, void 0, void 0, function* () {
918
+ return yield this.redis.keys('*');
919
+ });
920
+ }
921
+ /**
922
+ * CacheBackend.has implementation
923
+ */
924
+ has(key) {
925
+ return __awaiter(this, void 0, void 0, function* () {
926
+ return (yield this.redis.exists(key)) > 0; // 1: exists / 0: does not exist
927
+ });
928
+ }
929
+ /**
930
+ * CacheBackend.del implementation
931
+ */
932
+ del(key) {
933
+ return __awaiter(this, void 0, void 0, function* () {
934
+ return (yield this.redis.del(key)) === 1; // number of keys removed
935
+ });
936
+ }
937
+ /**
938
+ * CacheBackend.expire implementation
939
+ */
940
+ expire(key, ttl) {
941
+ return __awaiter(this, void 0, void 0, function* () {
942
+ const ret = ttl > 0 ? yield this.redis.expire(key, ttl) : yield this.redis.persist(key);
943
+ return ret > 0; // 1: success / 0: key does not exist
944
+ });
945
+ }
946
+ /**
947
+ * CacheBackend.ttl implementation
948
+ */
949
+ ttl(key) {
950
+ return __awaiter(this, void 0, void 0, function* () {
951
+ const ms = yield this.redis.pttl(key); // -2: key does not exist / -1: no timeout
952
+ if (ms >= 0)
953
+ return ms;
954
+ if (ms === -1)
955
+ return 0;
956
+ });
957
+ }
958
+ /**
959
+ * CacheBackend.close implementation
960
+ */
961
+ close() {
962
+ return __awaiter(this, void 0, void 0, function* () {
963
+ yield this.redis.quit();
964
+ });
965
+ }
966
+ }
967
+ //# sourceMappingURL=cache-service.js.map