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.
- package/README.md +1 -0
- package/dist/controllers/dummy-controller.d.ts +1 -1
- package/dist/controllers/dummy-controller.js +2 -2
- package/dist/controllers/dummy-controller.js.map +1 -1
- package/dist/controllers/general-api-controller.d.ts +1 -1
- package/dist/cores/api/api-service.d.ts +239 -0
- package/dist/cores/api/api-service.js +674 -0
- package/dist/cores/api/api-service.js.map +1 -0
- package/dist/cores/api/index.d.ts +10 -0
- package/dist/cores/api/index.js +27 -0
- package/dist/cores/api/index.js.map +1 -0
- package/dist/cores/aws/aws-kms-service.d.ts +53 -2
- package/dist/cores/aws/aws-kms-service.js +104 -17
- package/dist/cores/aws/aws-kms-service.js.map +1 -1
- package/dist/cores/cache/cache-service.d.ts +440 -0
- package/dist/cores/cache/cache-service.js +967 -0
- package/dist/cores/cache/cache-service.js.map +1 -0
- package/dist/cores/cache/index.d.ts +10 -0
- package/dist/cores/cache/index.js +27 -0
- package/dist/cores/cache/index.js.map +1 -0
- package/dist/cores/cache-service.d.ts +54 -18
- package/dist/cores/cache-service.js +37 -26
- package/dist/cores/cache-service.js.map +1 -1
- package/dist/cores/core-types.d.ts +42 -12
- package/dist/cores/dynamo/dynamo-query-service.d.ts +52 -0
- package/dist/cores/dynamo/dynamo-query-service.js +127 -0
- package/dist/cores/dynamo/dynamo-query-service.js.map +1 -0
- package/dist/cores/dynamo/dynamo-scan-service.d.ts +70 -0
- package/dist/cores/dynamo/dynamo-scan-service.js +164 -0
- package/dist/cores/dynamo/dynamo-scan-service.js.map +1 -0
- package/dist/cores/dynamo/dynamo-service.d.ts +192 -0
- package/dist/cores/dynamo/dynamo-service.js +525 -0
- package/dist/cores/dynamo/dynamo-service.js.map +1 -0
- package/dist/cores/dynamo/index.d.ts +12 -0
- package/dist/cores/dynamo/index.js +29 -0
- package/dist/cores/dynamo/index.js.map +1 -0
- package/dist/cores/elastic/elastic6-query-service.d.ts +104 -0
- package/dist/cores/elastic/elastic6-query-service.js +510 -0
- package/dist/cores/elastic/elastic6-query-service.js.map +1 -0
- package/dist/cores/elastic/elastic6-service.d.ts +273 -0
- package/dist/cores/elastic/elastic6-service.js +903 -0
- package/dist/cores/elastic/elastic6-service.js.map +1 -0
- package/dist/cores/elastic/hangul-service.d.ts +102 -0
- package/dist/cores/elastic/hangul-service.js +205 -0
- package/dist/cores/elastic/hangul-service.js.map +1 -0
- package/dist/cores/elastic/index.d.ts +12 -0
- package/dist/cores/elastic/index.js +29 -0
- package/dist/cores/elastic/index.js.map +1 -0
- package/dist/cores/hangul-service.d.ts +17 -3
- package/dist/cores/hangul-service.js +17 -8
- package/dist/cores/hangul-service.js.map +1 -1
- package/dist/cores/index.d.ts +5 -11
- package/dist/cores/index.js +8 -13
- package/dist/cores/index.js.map +1 -1
- package/dist/cores/lambda/lambda-dynamo-stream-handler.d.ts +2 -2
- package/dist/cores/lambda/lambda-web-handler.d.ts +158 -8
- package/dist/cores/lambda/lambda-web-handler.js +283 -77
- package/dist/cores/lambda/lambda-web-handler.js.map +1 -1
- package/dist/cores/protocol/protocol-service.d.ts +4 -0
- package/dist/cores/protocol/protocol-service.js +12 -7
- package/dist/cores/protocol/protocol-service.js.map +1 -1
- package/dist/cores/storage/http-storage-service.d.ts +22 -0
- package/dist/cores/storage/http-storage-service.js +129 -0
- package/dist/cores/storage/http-storage-service.js.map +1 -0
- package/dist/cores/storage/index.d.ts +14 -0
- package/dist/cores/storage/index.js +31 -0
- package/dist/cores/storage/index.js.map +1 -0
- package/dist/cores/storage/model-manager.d.ts +93 -0
- package/dist/cores/storage/model-manager.js +192 -0
- package/dist/cores/storage/model-manager.js.map +1 -0
- package/dist/cores/storage/proxy-storage-service.d.ts +573 -0
- package/dist/cores/storage/proxy-storage-service.js +913 -0
- package/dist/cores/storage/proxy-storage-service.js.map +1 -0
- package/dist/cores/storage/redis-storage-service.d.ts +183 -0
- package/dist/cores/storage/redis-storage-service.js +391 -0
- package/dist/cores/storage/redis-storage-service.js.map +1 -0
- package/dist/cores/storage/storage-service.d.ts +169 -0
- package/dist/cores/storage/storage-service.js +374 -0
- package/dist/cores/storage/storage-service.js.map +1 -0
- package/dist/cores/storage-service.d.ts +1 -1
- package/dist/cores/storage-service.js +2 -2
- package/dist/cores/storage-service.js.map +1 -1
- package/dist/engine/utilities.d.ts +4 -3
- package/dist/engine/utilities.js +6 -6
- package/dist/engine/utilities.js.map +1 -1
- package/dist/environ.d.ts +2 -2
- package/dist/environ.js +7 -4
- package/dist/environ.js.map +1 -1
- package/dist/extended/abstract-service.d.ts +533 -0
- package/dist/extended/abstract-service.js +915 -0
- package/dist/extended/abstract-service.js.map +1 -0
- package/dist/extended/index.d.ts +10 -0
- package/dist/extended/index.js +27 -0
- package/dist/extended/index.js.map +1 -0
- package/dist/helpers/helpers.d.ts +7 -0
- package/dist/helpers/helpers.js +7 -0
- package/dist/helpers/helpers.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- 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
|