@ti-engine/core 1.12.3 → 1.13.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/CHANGELOG.md +7 -0
- package/bin/settings.json +1 -0
- package/components/cache/cache-capability.js +42 -0
- package/components/cache/cache-provider.js +432 -0
- package/components/cache/redis-cache-provider.js +749 -0
- package/package.json +13 -1
- package/types/components/cache/cache-capability.d.ts +23 -0
- package/types/components/cache/cache-provider.d.ts +326 -0
- package/types/components/cache/redis-cache-provider.d.ts +327 -0
- package/types/utils/cache.d.ts +40 -32
- package/types/utils/config.d.ts +1 -0
- package/utils/cache.js +126 -388
- package/utils/config.js +7 -0
package/utils/cache.js
CHANGED
|
@@ -16,63 +16,36 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
const ConnectionObserver = require( "#connection-observer" );
|
|
19
|
+
const RedisCacheProvider = require( "#redis-cache-provider" );
|
|
19
20
|
const _ = require( "lodash" );
|
|
20
21
|
const config = require( "#config" );
|
|
21
|
-
const tools = require( "#tools" );
|
|
22
|
-
const redis = require( "#redis-integration" );
|
|
23
22
|
const exceptions = require( "#exceptions" );
|
|
23
|
+
const { cacheCapability } = require( "#cache-capability" );
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
26
|
+
* Determines which of the required capabilities a backend does not provide.
|
|
27
27
|
* <br/>
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
* This lives outside the class, and is shared by {@link CommonMemoryCache#getValue} and
|
|
32
|
-
* {@link CommonMemoryCache#getValues}, because it previously existed as two near-identical inline expressions and one
|
|
33
|
-
* of them drifted: `getValues` inspected its own accumulator instead of the per-key entry, so `.length` was
|
|
34
|
-
* `undefined`, the comparison was always false, and **every key resolved to `null`** whatever Redis returned.
|
|
28
|
+
* NOTE: This lives outside the class, and is exported, for the same reason the Redis decoders are: the cache singleton
|
|
29
|
+
* builds its own backend in its constructor, so the reconciliation cannot be driven without a live server. This is the
|
|
30
|
+
* pure half of it, and it is the half that decides whether an instance starts.
|
|
35
31
|
*
|
|
36
32
|
* @method
|
|
37
|
-
* @param {
|
|
38
|
-
* @
|
|
39
|
-
* @
|
|
40
|
-
|
|
41
|
-
function decodeCommandValue( result ) {
|
|
42
|
-
// `Array.isArray` rather than a bare truthy-and-length test: a string also has a `length` and an indexable
|
|
43
|
-
// character at 1, so a malformed non-array entry would otherwise be parsed as if it were a value.
|
|
44
|
-
return ( Array.isArray( result ) && result.length > 1 && _.isString( result[ 1 ] ) ) ? tools.parseJSON( result[ 1 ] ) : undefined;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Maps a set of requested keys onto the values a `multi(...).exec()` returned for them, using `null` for a miss.
|
|
49
|
-
* <br/>
|
|
50
|
-
* Iterates the requested `keys` rather than the raw results, so a short or absent response still yields one entry per
|
|
51
|
-
* requested key instead of silently omitting some — the caller's map always has the shape it asked for.
|
|
52
|
-
* <br/>
|
|
53
|
-
* The accumulator has no prototype on purpose: the key names come from the caller, and a cache key named `__proto__`
|
|
54
|
-
* written by bracket assignment onto an ordinary `{}` would repoint the accumulator's prototype instead of creating
|
|
55
|
-
* the entry. Same class as the `decycle` defect fixed in `tools.js`; see the 1.11.0 changelog entry.
|
|
56
|
-
*
|
|
57
|
-
* @method
|
|
58
|
-
* @param {string[]} keys The keys that were requested, in command order.
|
|
59
|
-
* @param {Array} [rawResults] The `multi(...).exec()` result.
|
|
60
|
-
* @returns {Object} A null-prototype map of key to value, `null` where the key was absent.
|
|
61
|
-
* @private
|
|
33
|
+
* @param {string[]} [required] Capabilities the application declared it needs.
|
|
34
|
+
* @param {string[]} [available] Capabilities the backend reports it provides.
|
|
35
|
+
* @returns {string[]} The required capabilities that are absent, in the order they were required.
|
|
36
|
+
* @public
|
|
62
37
|
*/
|
|
63
|
-
function
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
_.forEach( keys, ( key, idx ) => {
|
|
67
|
-
let decoded = decodeCommandValue( rawResults ? rawResults[ idx ] : undefined );
|
|
68
|
-
values[ key ] = ( decoded === undefined ) ? null : decoded;
|
|
69
|
-
} );
|
|
70
|
-
|
|
71
|
-
return values;
|
|
38
|
+
function findMissingCapabilities( required, available ) {
|
|
39
|
+
let provided = new Set( Array.isArray( available ) ? available : [] );
|
|
40
|
+
return _.filter( Array.isArray( required ) ? required : [], ( capability ) => provided.has( capability ) === false );
|
|
72
41
|
}
|
|
73
42
|
|
|
74
43
|
/**
|
|
75
44
|
* Used to create and/or return a Common Memory Cache singleton instance.
|
|
45
|
+
* <br/>
|
|
46
|
+
* NOTE: This owns the cache's operational state and the connection observation around it; where the values actually
|
|
47
|
+
* live is the {@link CacheProvider}'s business. Every method here checks that the cache is usable and then delegates,
|
|
48
|
+
* which is why no provider repeats that check.
|
|
76
49
|
*
|
|
77
50
|
* @class CommonMemoryCache
|
|
78
51
|
* @extends ConnectionObserver
|
|
@@ -82,7 +55,8 @@ function mapCommandValues( keys, rawResults ) {
|
|
|
82
55
|
class CommonMemoryCache extends ConnectionObserver {
|
|
83
56
|
|
|
84
57
|
static #instance = null;
|
|
85
|
-
|
|
58
|
+
/** @type CacheProvider */
|
|
59
|
+
#provider = null;
|
|
86
60
|
#isOperational = false;
|
|
87
61
|
#connectionIdentifier = "system-cache";
|
|
88
62
|
|
|
@@ -94,8 +68,8 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
94
68
|
super();
|
|
95
69
|
|
|
96
70
|
if ( !CommonMemoryCache.#instance ) {
|
|
97
|
-
this.#
|
|
98
|
-
this.#
|
|
71
|
+
this.#provider = new RedisCacheProvider( this.#connectionIdentifier );
|
|
72
|
+
this.#provider.addConnectionObserver( this );
|
|
99
73
|
|
|
100
74
|
CommonMemoryCache.#instance = this;
|
|
101
75
|
}
|
|
@@ -126,21 +100,56 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
126
100
|
return this.#connectionIdentifier;
|
|
127
101
|
}
|
|
128
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Property returning the optional behaviors the configured backend provides.
|
|
105
|
+
* <br/>
|
|
106
|
+
* NOTE: Accurate only once {@link CommonMemoryCache#initialize} has resolved — some capabilities cannot be
|
|
107
|
+
* established until the backend has connected.
|
|
108
|
+
*
|
|
109
|
+
* @property
|
|
110
|
+
* @returns {string[]} Values drawn from {@link TiCacheCapability}.
|
|
111
|
+
* @public
|
|
112
|
+
*/
|
|
113
|
+
get capabilities() {
|
|
114
|
+
return this.#provider.capabilities;
|
|
115
|
+
}
|
|
116
|
+
|
|
129
117
|
/**
|
|
130
118
|
* Used to initialize the cache service.
|
|
119
|
+
* <br/>
|
|
120
|
+
* NOTE: Once the backend is connected, the capabilities it reports are reconciled against the
|
|
121
|
+
* 'memoryCache.requiredCapabilities' setting, and startup fails if any of them is missing. That is deliberate: a
|
|
122
|
+
* backend silently lacking a behavior the application depends on is otherwise discovered from inside a request,
|
|
123
|
+
* long after the deployment that introduced it.
|
|
124
|
+
* <br/>
|
|
125
|
+
* NOTE: A failed reconciliation rolls the cache back to non-operational and shuts the backend down before it
|
|
126
|
+
* rejects, so a refused startup never leaves a usable cache behind.
|
|
131
127
|
*
|
|
132
128
|
* @method
|
|
133
129
|
* @returns {Promise}
|
|
130
|
+
* @throws {TiException.E_GEN_FEATURE_UNSUPPORTED} If the backend does not provide every required capability.
|
|
134
131
|
* @public
|
|
135
132
|
*/
|
|
136
133
|
initialize() {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
134
|
+
return this.#provider.initialize().then( () => {
|
|
135
|
+
try {
|
|
136
|
+
this.#verifyRequiredCapabilities();
|
|
137
|
+
} catch ( error ) {
|
|
138
|
+
// The backend is already connected and this cache already operational by the time the check runs:
|
|
139
|
+
// the Redis client notifies its connection observers from inside its "ready" handler, before
|
|
140
|
+
// `initialize()` resolves, and `onConnectionRecovered` sets `#isOperational` to true. Rejecting
|
|
141
|
+
// without undoing that would leave the singleton reporting an operational cache over a live
|
|
142
|
+
// connection while its caller has been told that startup failed - `ServiceInstance.onStart` only
|
|
143
|
+
// propagates the rejection, and `shutDown()` is reached from `stop()`, which a failed start never
|
|
144
|
+
// gets to. So the rollback belongs here, where the failure is raised.
|
|
145
|
+
this.#isOperational = false;
|
|
146
|
+
return this.#provider.shutDown().catch( () => {
|
|
147
|
+
// A backend that cannot close cleanly must not replace the reason startup was refused.
|
|
148
|
+
} ).then( () => {
|
|
149
|
+
throw error;
|
|
150
|
+
} );
|
|
151
|
+
}
|
|
152
|
+
} );
|
|
144
153
|
}
|
|
145
154
|
|
|
146
155
|
/**
|
|
@@ -151,7 +160,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
151
160
|
* @public
|
|
152
161
|
*/
|
|
153
162
|
shutDown() {
|
|
154
|
-
return this.#
|
|
163
|
+
return this.#provider.shutDown();
|
|
155
164
|
}
|
|
156
165
|
|
|
157
166
|
/**
|
|
@@ -200,14 +209,14 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
200
209
|
}
|
|
201
210
|
|
|
202
211
|
/**
|
|
203
|
-
* Used to register a new {@link ConnectionObserver} for events related to the underlying
|
|
212
|
+
* Used to register a new {@link ConnectionObserver} for events related to the underlying backend connection state.
|
|
204
213
|
*
|
|
205
214
|
* @method
|
|
206
215
|
* @param {ConnectionObserver} connectionObserver The {@link ConnectionObserver} that will be notified of any changes.
|
|
207
216
|
* @public
|
|
208
217
|
*/
|
|
209
218
|
addConnectionObserver( connectionObserver ) {
|
|
210
|
-
this.#
|
|
219
|
+
this.#provider.addConnectionObserver( connectionObserver );
|
|
211
220
|
}
|
|
212
221
|
|
|
213
222
|
/**
|
|
@@ -219,19 +228,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
219
228
|
* @public
|
|
220
229
|
*/
|
|
221
230
|
matchKeys( pattern ) {
|
|
222
|
-
return
|
|
223
|
-
if ( this.#isOperational === true ) {
|
|
224
|
-
let commandKeys = [ redis.cacheCommands.KEYS, pattern ];
|
|
225
|
-
this.#redisClient.executeCommands( [ commandKeys ] ).then( ( results ) => {
|
|
226
|
-
results = results[ 0 ];
|
|
227
|
-
resolve( ( results && results.length > 1 ) ? results[ 1 ] : [] );
|
|
228
|
-
} ).catch( ( error ) => {
|
|
229
|
-
reject( error );
|
|
230
|
-
} );
|
|
231
|
-
} else {
|
|
232
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
233
|
-
}
|
|
234
|
-
} );
|
|
231
|
+
return this.#guarded( () => this.#provider.matchKeys( pattern ) );
|
|
235
232
|
}
|
|
236
233
|
|
|
237
234
|
/**
|
|
@@ -245,26 +242,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
245
242
|
* @public
|
|
246
243
|
*/
|
|
247
244
|
setValue( key, value, expiration ) {
|
|
248
|
-
return
|
|
249
|
-
if ( this.#isOperational === true ) {
|
|
250
|
-
if ( value ) {
|
|
251
|
-
let commandSetValue = [ redis.cacheCommands.SET_VALUE, key, tools.stringifyJSON( value ) ];
|
|
252
|
-
if ( expiration ) {
|
|
253
|
-
commandSetValue.push( "EX" );
|
|
254
|
-
commandSetValue.push( expiration );
|
|
255
|
-
}
|
|
256
|
-
this.#redisClient.executeCommands( [ commandSetValue ] ).then( () => {
|
|
257
|
-
resolve( value );
|
|
258
|
-
} ).catch( ( error ) => {
|
|
259
|
-
reject( error );
|
|
260
|
-
} );
|
|
261
|
-
} else {
|
|
262
|
-
resolve( value );
|
|
263
|
-
}
|
|
264
|
-
} else {
|
|
265
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
266
|
-
}
|
|
267
|
-
} );
|
|
245
|
+
return this.#guarded( () => this.#provider.setValue( key, value, expiration ) );
|
|
268
246
|
}
|
|
269
247
|
|
|
270
248
|
/**
|
|
@@ -278,31 +256,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
278
256
|
* @public
|
|
279
257
|
*/
|
|
280
258
|
setValues( keyValues, prefix, expiration ) {
|
|
281
|
-
return
|
|
282
|
-
if ( this.#isOperational === true ) {
|
|
283
|
-
if ( keyValues ) {
|
|
284
|
-
let commands = [];
|
|
285
|
-
_.forEach( keyValues, ( value, key ) => {
|
|
286
|
-
let commandSetValue = [ redis.cacheCommands.SET_VALUE, ( ( prefix ) ? prefix : "" ) + key, tools.stringifyJSON( value ) ];
|
|
287
|
-
if ( expiration ) {
|
|
288
|
-
commandSetValue.push( "EX" );
|
|
289
|
-
commandSetValue.push( expiration );
|
|
290
|
-
}
|
|
291
|
-
commands.push( commandSetValue );
|
|
292
|
-
} );
|
|
293
|
-
|
|
294
|
-
this.#redisClient.executeCommands( commands ).then( () => {
|
|
295
|
-
resolve( keyValues );
|
|
296
|
-
} ).catch( ( error ) => {
|
|
297
|
-
reject( error );
|
|
298
|
-
} );
|
|
299
|
-
} else {
|
|
300
|
-
resolve( keyValues );
|
|
301
|
-
}
|
|
302
|
-
} else {
|
|
303
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
304
|
-
}
|
|
305
|
-
} );
|
|
259
|
+
return this.#guarded( () => this.#provider.setValues( keyValues, prefix, expiration ) );
|
|
306
260
|
}
|
|
307
261
|
|
|
308
262
|
/**
|
|
@@ -314,18 +268,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
314
268
|
* @public
|
|
315
269
|
*/
|
|
316
270
|
getValue( key ) {
|
|
317
|
-
return
|
|
318
|
-
if ( this.#isOperational === true ) {
|
|
319
|
-
let commandGetValue = [ redis.cacheCommands.GET_VALUE, key ];
|
|
320
|
-
this.#redisClient.executeCommands( [ commandGetValue ] ).then( ( results ) => {
|
|
321
|
-
resolve( decodeCommandValue( results ? results[ 0 ] : undefined ) );
|
|
322
|
-
} ).catch( ( error ) => {
|
|
323
|
-
reject( error );
|
|
324
|
-
} );
|
|
325
|
-
} else {
|
|
326
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
327
|
-
}
|
|
328
|
-
} );
|
|
271
|
+
return this.#guarded( () => this.#provider.getValue( key ) );
|
|
329
272
|
}
|
|
330
273
|
|
|
331
274
|
/**
|
|
@@ -338,21 +281,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
338
281
|
* @public
|
|
339
282
|
*/
|
|
340
283
|
getValues( keys, prefix ) {
|
|
341
|
-
return
|
|
342
|
-
if ( this.#isOperational === true ) {
|
|
343
|
-
let commands = [];
|
|
344
|
-
_.forEach( keys, ( key ) => {
|
|
345
|
-
commands.push( [ redis.cacheCommands.GET_VALUE, ( ( prefix ) ? prefix : "" ) + key ] );
|
|
346
|
-
} );
|
|
347
|
-
this.#redisClient.executeCommands( commands ).then( ( rawResults ) => {
|
|
348
|
-
resolve( mapCommandValues( keys, rawResults ) );
|
|
349
|
-
} ).catch( ( error ) => {
|
|
350
|
-
reject( error );
|
|
351
|
-
} );
|
|
352
|
-
} else {
|
|
353
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
354
|
-
}
|
|
355
|
-
} );
|
|
284
|
+
return this.#guarded( () => this.#provider.getValues( keys, prefix ) );
|
|
356
285
|
}
|
|
357
286
|
|
|
358
287
|
/**
|
|
@@ -364,19 +293,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
364
293
|
* @public
|
|
365
294
|
*/
|
|
366
295
|
deleteValue( key ) {
|
|
367
|
-
return
|
|
368
|
-
if ( this.#isOperational === true ) {
|
|
369
|
-
let commandDeleteValue = [ redis.cacheCommands.DELETE_VALUE, key ];
|
|
370
|
-
this.#redisClient.executeCommands( [ commandDeleteValue ] ).then( ( results ) => {
|
|
371
|
-
results = results[ 0 ];
|
|
372
|
-
resolve( ( results && results.length > 1 ) ? results[ 1 ] : undefined );
|
|
373
|
-
} ).catch( ( error ) => {
|
|
374
|
-
reject( error );
|
|
375
|
-
} );
|
|
376
|
-
} else {
|
|
377
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
378
|
-
}
|
|
379
|
-
} );
|
|
296
|
+
return this.#guarded( () => this.#provider.deleteValue( key ) );
|
|
380
297
|
}
|
|
381
298
|
|
|
382
299
|
/**
|
|
@@ -392,18 +309,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
392
309
|
* @public
|
|
393
310
|
*/
|
|
394
311
|
expireValue( key, seconds, name ) {
|
|
395
|
-
return
|
|
396
|
-
if ( this.#isOperational === true ) {
|
|
397
|
-
let commandExpire = ( name ) ? [ redis.cacheCommands.HASH_EXPIRE, name, seconds, "FIELDS", 1, key ] : [ redis.cacheCommands.EXPIRE, key, seconds ];
|
|
398
|
-
this.#redisClient.executeCommands( [ commandExpire ] ).then( () => {
|
|
399
|
-
resolve( seconds );
|
|
400
|
-
} ).catch( ( error ) => {
|
|
401
|
-
reject( error );
|
|
402
|
-
} );
|
|
403
|
-
} else {
|
|
404
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
405
|
-
}
|
|
406
|
-
} );
|
|
312
|
+
return this.#guarded( () => this.#provider.expireValue( key, seconds, name ) );
|
|
407
313
|
}
|
|
408
314
|
|
|
409
315
|
/**
|
|
@@ -416,24 +322,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
416
322
|
* @public
|
|
417
323
|
*/
|
|
418
324
|
listPushValue( listName, values ) {
|
|
419
|
-
return
|
|
420
|
-
if ( this.#isOperational === true ) {
|
|
421
|
-
let commandPushValues = [ redis.cacheCommands.LIST_PUSH, listName ];
|
|
422
|
-
_.forEach( values, ( value ) => {
|
|
423
|
-
if ( value ) {
|
|
424
|
-
commandPushValues.push( tools.stringifyJSON( value ) );
|
|
425
|
-
}
|
|
426
|
-
} );
|
|
427
|
-
this.#redisClient.executeCommands( [ commandPushValues ] ).then( ( results ) => {
|
|
428
|
-
results = results[ 0 ];
|
|
429
|
-
resolve( ( results && results.length > 1 ) ? results[ 1 ] : undefined );
|
|
430
|
-
} ).catch( ( error ) => {
|
|
431
|
-
reject( error );
|
|
432
|
-
} );
|
|
433
|
-
} else {
|
|
434
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
435
|
-
}
|
|
436
|
-
} );
|
|
325
|
+
return this.#guarded( () => this.#provider.listPushValue( listName, values ) );
|
|
437
326
|
}
|
|
438
327
|
|
|
439
328
|
/**
|
|
@@ -446,18 +335,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
446
335
|
* @public
|
|
447
336
|
*/
|
|
448
337
|
addToSet( key, value ) {
|
|
449
|
-
return
|
|
450
|
-
if ( this.#isOperational === true ) {
|
|
451
|
-
let commandAddToSet = [ redis.cacheCommands.ADD_TO_SET, key, tools.stringifyJSON( value ) ];
|
|
452
|
-
this.#redisClient.executeCommands( [ commandAddToSet ] ).then( () => {
|
|
453
|
-
resolve();
|
|
454
|
-
} ).catch( ( error ) => {
|
|
455
|
-
reject( error );
|
|
456
|
-
} );
|
|
457
|
-
} else {
|
|
458
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
459
|
-
}
|
|
460
|
-
} );
|
|
338
|
+
return this.#guarded( () => this.#provider.addToSet( key, value ) );
|
|
461
339
|
}
|
|
462
340
|
|
|
463
341
|
/**
|
|
@@ -472,21 +350,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
472
350
|
* @public
|
|
473
351
|
*/
|
|
474
352
|
addToSetMulti( keys, values ) {
|
|
475
|
-
return
|
|
476
|
-
if ( this.#isOperational === true ) {
|
|
477
|
-
let commands = [];
|
|
478
|
-
_.forEach( keys, ( key, idx ) => {
|
|
479
|
-
commands.push( [ redis.cacheCommands.ADD_TO_SET, key, tools.stringifyJSON( values[ idx ] ) ] );
|
|
480
|
-
} );
|
|
481
|
-
this.#redisClient.executeCommands( commands ).then( () => {
|
|
482
|
-
resolve();
|
|
483
|
-
} ).catch( ( error ) => {
|
|
484
|
-
reject( error );
|
|
485
|
-
} );
|
|
486
|
-
} else {
|
|
487
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
488
|
-
}
|
|
489
|
-
} );
|
|
353
|
+
return this.#guarded( () => this.#provider.addToSetMulti( keys, values ) );
|
|
490
354
|
}
|
|
491
355
|
|
|
492
356
|
/**
|
|
@@ -499,20 +363,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
499
363
|
* @public
|
|
500
364
|
*/
|
|
501
365
|
isSetMember( setName, value ) {
|
|
502
|
-
return
|
|
503
|
-
if ( this.#isOperational === true ) {
|
|
504
|
-
let commandIsSetMember = [ redis.cacheCommands.IS_SET_MEMBER, setName, value ];
|
|
505
|
-
this.#redisClient.executeCommands( [ commandIsSetMember ] ).then( ( results ) => {
|
|
506
|
-
results = results[ 0 ];
|
|
507
|
-
let result = !!( results && results.length > 1 && results[ 1 ] === 1 );
|
|
508
|
-
resolve( result );
|
|
509
|
-
} ).catch( ( error ) => {
|
|
510
|
-
reject( error );
|
|
511
|
-
} );
|
|
512
|
-
} else {
|
|
513
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
514
|
-
}
|
|
515
|
-
} );
|
|
366
|
+
return this.#guarded( () => this.#provider.isSetMember( setName, value ) );
|
|
516
367
|
}
|
|
517
368
|
|
|
518
369
|
/**
|
|
@@ -524,20 +375,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
524
375
|
* @public
|
|
525
376
|
*/
|
|
526
377
|
membersOfSet( key ) {
|
|
527
|
-
return
|
|
528
|
-
if ( this.#isOperational === true ) {
|
|
529
|
-
let commandMembersOfSet = [ redis.cacheCommands.GET_ALL_FROM_SET, key ];
|
|
530
|
-
this.#redisClient.executeCommands( [ commandMembersOfSet ] ).then( ( results ) => {
|
|
531
|
-
results = results[ 0 ];
|
|
532
|
-
let parsedResults = ( results && results.length > 1 && results[ 1 ] ) ? results[ 1 ] : [];
|
|
533
|
-
resolve( parsedResults );
|
|
534
|
-
} ).catch( ( error ) => {
|
|
535
|
-
reject( error );
|
|
536
|
-
} );
|
|
537
|
-
} else {
|
|
538
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
539
|
-
}
|
|
540
|
-
} );
|
|
378
|
+
return this.#guarded( () => this.#provider.membersOfSet( key ) );
|
|
541
379
|
}
|
|
542
380
|
|
|
543
381
|
/**
|
|
@@ -549,20 +387,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
549
387
|
* @public
|
|
550
388
|
*/
|
|
551
389
|
unionOfSets( keys ) {
|
|
552
|
-
return
|
|
553
|
-
if ( this.#isOperational === true ) {
|
|
554
|
-
let commandUnionOfSets = _.concat( [ redis.cacheCommands.UNION_OF_SETS ], keys );
|
|
555
|
-
this.#redisClient.executeCommands( [ commandUnionOfSets ] ).then( ( results ) => {
|
|
556
|
-
results = results[ 0 ];
|
|
557
|
-
let parsedResults = ( results && results.length > 1 && results[ 1 ] ) ? results[ 1 ] : [];
|
|
558
|
-
resolve( parsedResults );
|
|
559
|
-
} ).catch( ( error ) => {
|
|
560
|
-
reject( error );
|
|
561
|
-
} );
|
|
562
|
-
} else {
|
|
563
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
564
|
-
}
|
|
565
|
-
} );
|
|
390
|
+
return this.#guarded( () => this.#provider.unionOfSets( keys ) );
|
|
566
391
|
}
|
|
567
392
|
|
|
568
393
|
/**
|
|
@@ -577,18 +402,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
577
402
|
* @public
|
|
578
403
|
*/
|
|
579
404
|
hashSetField( key, name, value ) {
|
|
580
|
-
return
|
|
581
|
-
if ( this.#isOperational === true ) {
|
|
582
|
-
let commandHashSetField = [ redis.cacheCommands.HASH_SET, key, name, tools.stringifyJSON( value ) ];
|
|
583
|
-
this.#redisClient.executeCommands( [ commandHashSetField ] ).then( () => {
|
|
584
|
-
resolve();
|
|
585
|
-
} ).catch( ( error ) => {
|
|
586
|
-
reject( error );
|
|
587
|
-
} );
|
|
588
|
-
} else {
|
|
589
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
590
|
-
}
|
|
591
|
-
} );
|
|
405
|
+
return this.#guarded( () => this.#provider.hashSetField( key, name, value ) );
|
|
592
406
|
}
|
|
593
407
|
|
|
594
408
|
/**
|
|
@@ -604,22 +418,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
604
418
|
* @public
|
|
605
419
|
*/
|
|
606
420
|
hashSetFields( key, fields ) {
|
|
607
|
-
return
|
|
608
|
-
if ( this.#isOperational === true ) {
|
|
609
|
-
let commandHashSetFields = [ redis.cacheCommands.HASH_SET, key ];
|
|
610
|
-
_.forEach( fields, ( field ) => {
|
|
611
|
-
commandHashSetFields.push( field.name );
|
|
612
|
-
commandHashSetFields.push( tools.stringifyJSON( field.value ) );
|
|
613
|
-
} );
|
|
614
|
-
this.#redisClient.executeCommands( [ commandHashSetFields ] ).then( () => {
|
|
615
|
-
resolve();
|
|
616
|
-
} ).catch( ( error ) => {
|
|
617
|
-
reject( error );
|
|
618
|
-
} );
|
|
619
|
-
} else {
|
|
620
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
621
|
-
}
|
|
622
|
-
} );
|
|
421
|
+
return this.#guarded( () => this.#provider.hashSetFields( key, fields ) );
|
|
623
422
|
}
|
|
624
423
|
|
|
625
424
|
/**
|
|
@@ -632,19 +431,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
632
431
|
* @public
|
|
633
432
|
*/
|
|
634
433
|
hashGetField( key, field ) {
|
|
635
|
-
return
|
|
636
|
-
if ( this.#isOperational === true ) {
|
|
637
|
-
let commandHashGetField = [ redis.cacheCommands.HASH_GET, key, field ];
|
|
638
|
-
this.#redisClient.executeCommands( [ commandHashGetField ] ).then( ( results ) => {
|
|
639
|
-
results = results[ 0 ];
|
|
640
|
-
resolve( ( results && results.length > 1 && _.isString( results[ 1 ] ) ) ? tools.parseJSON( results[ 1 ] ) : null );
|
|
641
|
-
} ).catch( ( error ) => {
|
|
642
|
-
reject( error );
|
|
643
|
-
} );
|
|
644
|
-
} else {
|
|
645
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
646
|
-
}
|
|
647
|
-
} );
|
|
434
|
+
return this.#guarded( () => this.#provider.hashGetField( key, field ) );
|
|
648
435
|
}
|
|
649
436
|
|
|
650
437
|
/**
|
|
@@ -657,19 +444,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
657
444
|
* @public
|
|
658
445
|
*/
|
|
659
446
|
hashDeleteField( key, field ) {
|
|
660
|
-
return
|
|
661
|
-
if ( this.#isOperational === true ) {
|
|
662
|
-
let commandHashGetField = [ redis.cacheCommands.HASH_REMOVE, key, field ];
|
|
663
|
-
this.#redisClient.executeCommands( [ commandHashGetField ] ).then( ( results ) => {
|
|
664
|
-
results = results[ 0 ];
|
|
665
|
-
resolve( ( results && results.length > 1 ) ? tools.toBool( results[ 1 ] ) : false );
|
|
666
|
-
} ).catch( ( error ) => {
|
|
667
|
-
reject( error );
|
|
668
|
-
} );
|
|
669
|
-
} else {
|
|
670
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
671
|
-
}
|
|
672
|
-
} );
|
|
447
|
+
return this.#guarded( () => this.#provider.hashDeleteField( key, field ) );
|
|
673
448
|
}
|
|
674
449
|
|
|
675
450
|
/**
|
|
@@ -687,25 +462,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
687
462
|
* @public
|
|
688
463
|
*/
|
|
689
464
|
setJSON( key, value, path = "$", overrideMode = 0 ) {
|
|
690
|
-
return
|
|
691
|
-
if ( this.#isOperational === true ) {
|
|
692
|
-
if ( this.#redisClient.isJSONSupported ) {
|
|
693
|
-
let commandArguments = [ redis.cacheCommands.JSON_SET, key, this.#normalizeJSONPath( path ), tools.stringifyJSON( value ) ];
|
|
694
|
-
if ( overrideMode !== 0 ) {
|
|
695
|
-
commandArguments.push( overrideMode === 1 ? redis.cacheOverrideMode.NX : redis.cacheOverrideMode.XX );
|
|
696
|
-
}
|
|
697
|
-
this.#redisClient.callCommand( commandArguments ).then( () => {
|
|
698
|
-
resolve();
|
|
699
|
-
} ).catch( ( error ) => {
|
|
700
|
-
reject( error );
|
|
701
|
-
} );
|
|
702
|
-
} else {
|
|
703
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED, { details: "No RedisJSON module installed on server." } ) );
|
|
704
|
-
}
|
|
705
|
-
} else {
|
|
706
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
707
|
-
}
|
|
708
|
-
} );
|
|
465
|
+
return this.#guarded( () => this.#provider.setJSON( key, value, path, overrideMode ) );
|
|
709
466
|
}
|
|
710
467
|
|
|
711
468
|
/**
|
|
@@ -720,22 +477,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
720
477
|
* @public
|
|
721
478
|
*/
|
|
722
479
|
getJSON( key, path = "$" ) {
|
|
723
|
-
return
|
|
724
|
-
if ( this.#isOperational === true ) {
|
|
725
|
-
if ( this.#redisClient.isJSONSupported ) {
|
|
726
|
-
let commandArguments = [ redis.cacheCommands.JSON_GET, key, this.#normalizeJSONPath( path ) ];
|
|
727
|
-
this.#redisClient.callCommand( commandArguments ).then( ( result ) => {
|
|
728
|
-
resolve( result != null ? tools.parseJSON( String( result ) ) : null );
|
|
729
|
-
} ).catch( ( error ) => {
|
|
730
|
-
reject( error );
|
|
731
|
-
} );
|
|
732
|
-
} else {
|
|
733
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED, { details: "No RedisJSON module installed on server." } ) );
|
|
734
|
-
}
|
|
735
|
-
} else {
|
|
736
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
737
|
-
}
|
|
738
|
-
} );
|
|
480
|
+
return this.#guarded( () => this.#provider.getJSON( key, path ) );
|
|
739
481
|
}
|
|
740
482
|
|
|
741
483
|
/**
|
|
@@ -751,22 +493,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
751
493
|
* @public
|
|
752
494
|
*/
|
|
753
495
|
editJSON( key, value, path = "$" ) {
|
|
754
|
-
return
|
|
755
|
-
if ( this.#isOperational === true ) {
|
|
756
|
-
if ( this.#redisClient.isJSONSupported ) {
|
|
757
|
-
let commandArguments = [ redis.cacheCommands.JSON_MERGE, key, this.#normalizeJSONPath( path ), tools.stringifyJSON( value ) ];
|
|
758
|
-
this.#redisClient.callCommand( commandArguments ).then( () => {
|
|
759
|
-
resolve();
|
|
760
|
-
} ).catch( ( error ) => {
|
|
761
|
-
reject( error );
|
|
762
|
-
} );
|
|
763
|
-
} else {
|
|
764
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED, { details: "No RedisJSON module installed on server." } ) );
|
|
765
|
-
}
|
|
766
|
-
} else {
|
|
767
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
768
|
-
}
|
|
769
|
-
} );
|
|
496
|
+
return this.#guarded( () => this.#provider.editJSON( key, value, path ) );
|
|
770
497
|
}
|
|
771
498
|
|
|
772
499
|
/**
|
|
@@ -782,41 +509,45 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
782
509
|
* @public
|
|
783
510
|
*/
|
|
784
511
|
arrayAppendJSON( key, value, path = "$" ) {
|
|
785
|
-
return
|
|
786
|
-
if ( this.#isOperational === true ) {
|
|
787
|
-
if ( this.#redisClient.isJSONSupported ) {
|
|
788
|
-
let commandArguments = [ redis.cacheCommands.JSON_ARRAY_APPEND, key, this.#normalizeJSONPath( path ), tools.stringifyJSON( value ) ];
|
|
789
|
-
this.#redisClient.callCommand( commandArguments ).then( () => {
|
|
790
|
-
resolve();
|
|
791
|
-
} ).catch( ( error ) => {
|
|
792
|
-
reject( error );
|
|
793
|
-
} );
|
|
794
|
-
} else {
|
|
795
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED, { details: "No RedisJSON module installed on server." } ) );
|
|
796
|
-
}
|
|
797
|
-
} else {
|
|
798
|
-
reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
799
|
-
}
|
|
800
|
-
} );
|
|
512
|
+
return this.#guarded( () => this.#provider.arrayAppendJSON( key, value, path ) );
|
|
801
513
|
}
|
|
802
514
|
|
|
803
515
|
/* Private interface */
|
|
804
516
|
|
|
805
517
|
/**
|
|
806
|
-
* Used to
|
|
518
|
+
* Used to reject an operation when the cache is not usable, and to delegate it to the backend when it is.
|
|
807
519
|
* <br/>
|
|
808
|
-
* NOTE:
|
|
809
|
-
* which
|
|
520
|
+
* NOTE: This exists so the check lives in exactly one place. It previously stood at the head of all twenty-one
|
|
521
|
+
* data methods, which is twenty-one chances for a new method to be added without it.
|
|
810
522
|
*
|
|
811
523
|
* @method
|
|
812
|
-
* @param {
|
|
813
|
-
* @returns {
|
|
524
|
+
* @param {function(): Promise} operation
|
|
525
|
+
* @returns {Promise}
|
|
526
|
+
*/
|
|
527
|
+
#guarded( operation ) {
|
|
528
|
+
return ( this.#isOperational === true )
|
|
529
|
+
? operation()
|
|
530
|
+
: Promise.reject( exceptions.raise( exceptions.exceptionCode.E_GEN_SYSTEM_CACHE_UNAVAILABLE ) );
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Used to reconcile what the application requires against what the backend reports it can do.
|
|
535
|
+
*
|
|
536
|
+
* @method
|
|
537
|
+
* @throws {TiException.E_GEN_FEATURE_UNSUPPORTED} If any required capability is absent.
|
|
814
538
|
*/
|
|
815
|
-
#
|
|
816
|
-
|
|
817
|
-
|
|
539
|
+
#verifyRequiredCapabilities() {
|
|
540
|
+
let required = config.getSetting( config.setting.MEMORY_CACHE_REQUIRED_CAPABILITIES, [] );
|
|
541
|
+
if ( _.isEmpty( required ) === true ) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
let missing = findMissingCapabilities( required, this.#provider.capabilities );
|
|
546
|
+
if ( missing.length > 0 ) {
|
|
547
|
+
throw exceptions.raise( exceptions.exceptionCode.E_GEN_FEATURE_UNSUPPORTED, {
|
|
548
|
+
details: `The configured cache backend '${ this.#provider.constructor.name }' does not provide the required ${ ( missing.length === 1 ) ? "capability" : "capabilities" }: ${ missing.join( ", " ) }.`
|
|
549
|
+
} );
|
|
818
550
|
}
|
|
819
|
-
return ( path.startsWith( "$" ) === false ) ? ( "$." + path ) : path;
|
|
820
551
|
}
|
|
821
552
|
|
|
822
553
|
}
|
|
@@ -824,7 +555,14 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
824
555
|
const instance = new CommonMemoryCache();
|
|
825
556
|
module.exports.instance = Object.freeze( instance );
|
|
826
557
|
|
|
827
|
-
//
|
|
828
|
-
//
|
|
829
|
-
|
|
830
|
-
module.exports.
|
|
558
|
+
// Re-exported from the Redis backend, where these now live along with the rest of the Redis-specific decoding. They
|
|
559
|
+
// stay on this module because it is the published entry point and removing them would break any consumer that reaches
|
|
560
|
+
// for them - including this package's own `cache-get-values` suite.
|
|
561
|
+
module.exports.decodeCommandValue = RedisCacheProvider.decodeCommandValue;
|
|
562
|
+
module.exports.mapCommandValues = RedisCacheProvider.mapCommandValues;
|
|
563
|
+
|
|
564
|
+
// Re-exported so a consumer can name a capability without reaching past this module's exports map.
|
|
565
|
+
module.exports.cacheCapability = cacheCapability;
|
|
566
|
+
|
|
567
|
+
// Exported for testing, per the note on the function itself.
|
|
568
|
+
module.exports.findMissingCapabilities = findMissingCapabilities;
|