@expo/entity-cache-adapter-redis 0.41.0 → 0.42.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/build/GenericRedisCacher.d.ts +26 -4
- package/build/GenericRedisCacher.js +36 -5
- package/build/GenericRedisCacher.js.map +1 -1
- package/build/RedisCacheAdapterProvider.d.ts +1 -1
- package/build/RedisCacheAdapterProvider.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/index.js.map +1 -1
- package/build/tsconfig.build.tsbuildinfo +1 -0
- package/build/utils/getSurroundingCacheKeyVersionsForInvalidation.d.ts +1 -0
- package/build/utils/getSurroundingCacheKeyVersionsForInvalidation.js +11 -0
- package/build/utils/getSurroundingCacheKeyVersionsForInvalidation.js.map +1 -0
- package/package.json +9 -7
- package/src/GenericRedisCacher.ts +72 -13
- package/src/RedisCacheAdapterProvider.ts +3 -3
- package/src/__integration-tests__/BatchedRedisCacheAdapter-integration-test.ts +29 -11
- package/src/__integration-tests__/GenericRedisCacher-full-integration-test.ts +83 -18
- package/src/__integration-tests__/GenericRedisCacher-integration-test.ts +7 -3
- package/src/__integration-tests__/errors-test.ts +7 -3
- package/src/{testfixtures → __testfixtures__}/RedisTestEntity.ts +13 -29
- package/src/{testfixtures → __testfixtures__}/createRedisIntegrationTestEntityCompanionProvider.ts +1 -2
- package/src/__tests__/GenericRedisCacher-test.ts +68 -12
- package/src/index.ts +1 -0
- package/src/utils/__tests__/getSurroundingCacheKeyVersionsForInvalidation-test.ts +9 -0
- package/src/utils/getSurroundingCacheKeyVersionsForInvalidation.ts +9 -0
- package/build/__integration-tests__/BatchedRedisCacheAdapter-integration-test.d.ts +0 -1
- package/build/__integration-tests__/BatchedRedisCacheAdapter-integration-test.js +0 -122
- package/build/__integration-tests__/BatchedRedisCacheAdapter-integration-test.js.map +0 -1
- package/build/__integration-tests__/GenericRedisCacher-full-integration-test.d.ts +0 -1
- package/build/__integration-tests__/GenericRedisCacher-full-integration-test.js +0 -93
- package/build/__integration-tests__/GenericRedisCacher-full-integration-test.js.map +0 -1
- package/build/__integration-tests__/GenericRedisCacher-integration-test.d.ts +0 -1
- package/build/__integration-tests__/GenericRedisCacher-integration-test.js +0 -122
- package/build/__integration-tests__/GenericRedisCacher-integration-test.js.map +0 -1
- package/build/__integration-tests__/errors-test.d.ts +0 -1
- package/build/__integration-tests__/errors-test.js +0 -39
- package/build/__integration-tests__/errors-test.js.map +0 -1
- package/build/__tests__/GenericRedisCacher-test.d.ts +0 -1
- package/build/__tests__/GenericRedisCacher-test.js +0 -141
- package/build/__tests__/GenericRedisCacher-test.js.map +0 -1
- package/build/errors/__tests__/wrapNativeRedisCallAsync-test.d.ts +0 -1
- package/build/errors/__tests__/wrapNativeRedisCallAsync-test.js +0 -42
- package/build/errors/__tests__/wrapNativeRedisCallAsync-test.js.map +0 -1
- package/build/testfixtures/RedisTestEntity.d.ts +0 -16
- package/build/testfixtures/RedisTestEntity.js +0 -49
- package/build/testfixtures/RedisTestEntity.js.map +0 -1
- package/build/testfixtures/createRedisIntegrationTestEntityCompanionProvider.d.ts +0 -3
- package/build/testfixtures/createRedisIntegrationTestEntityCompanionProvider.js +0 -31
- package/build/testfixtures/createRedisIntegrationTestEntityCompanionProvider.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CacheLoadResult, EntityConfiguration, IEntityGenericCacher } from '@expo/entity';
|
|
1
|
+
import { CacheLoadResult, EntityConfiguration, IEntityGenericCacher, IEntityLoadKey, IEntityLoadValue } from '@expo/entity';
|
|
2
2
|
export interface IRedisTransaction {
|
|
3
3
|
set(key: string, value: string, secondsToken: 'EX', seconds: number): this;
|
|
4
4
|
exec(): Promise<any>;
|
|
@@ -8,6 +8,22 @@ export interface IRedis {
|
|
|
8
8
|
multi(): IRedisTransaction;
|
|
9
9
|
del(...args: [...keys: string[]]): Promise<any>;
|
|
10
10
|
}
|
|
11
|
+
/**
|
|
12
|
+
* The strategy for generating the set of cache keys to invalidate in the Redis cache after entity mutation.
|
|
13
|
+
*/
|
|
14
|
+
export declare enum RedisCacheInvalidationStrategy {
|
|
15
|
+
/**
|
|
16
|
+
* Invalidate just the cache key(s) for the current cacheKeyVersion of the entity.
|
|
17
|
+
*/
|
|
18
|
+
CURRENT_CACHE_KEY_VERSION = "current-cache-key-version",
|
|
19
|
+
/**
|
|
20
|
+
* Invalidate the cache key(s) for the current cacheKeyVersion and the surrounding cache key versions
|
|
21
|
+
* (e.g. `1`, `2` and `3` if the current version is `2`). This can be useful for deployment safety, where
|
|
22
|
+
* some machines may be operating on an old version of the code and thus an old cacheKeyVersion, and some the new version.
|
|
23
|
+
* This strategy generates cache keys for both old and potential future new versions.
|
|
24
|
+
*/
|
|
25
|
+
SURROUNDING_CACHE_KEY_VERSIONS = "surrounding-cache-key-versions"
|
|
26
|
+
}
|
|
11
27
|
export interface GenericRedisCacheContext {
|
|
12
28
|
/**
|
|
13
29
|
* Instance of ioredis.Redis
|
|
@@ -33,14 +49,20 @@ export interface GenericRedisCacheContext {
|
|
|
33
49
|
* this TTL will be assumed not present in the database (unless invalidated).
|
|
34
50
|
*/
|
|
35
51
|
ttlSecondsNegative: number;
|
|
52
|
+
/**
|
|
53
|
+
* Invalidation strategy for the cache.
|
|
54
|
+
*/
|
|
55
|
+
invalidationStrategy: RedisCacheInvalidationStrategy;
|
|
36
56
|
}
|
|
37
|
-
export default class GenericRedisCacher<TFields extends Record<string, any
|
|
57
|
+
export default class GenericRedisCacher<TFields extends Record<string, any>, TIDField extends keyof TFields> implements IEntityGenericCacher<TFields, TIDField> {
|
|
38
58
|
private readonly context;
|
|
39
59
|
private readonly entityConfiguration;
|
|
40
|
-
constructor(context: GenericRedisCacheContext, entityConfiguration: EntityConfiguration<TFields>);
|
|
60
|
+
constructor(context: GenericRedisCacheContext, entityConfiguration: EntityConfiguration<TFields, TIDField>);
|
|
41
61
|
loadManyAsync(keys: readonly string[]): Promise<ReadonlyMap<string, CacheLoadResult<TFields>>>;
|
|
42
62
|
cacheManyAsync(objectMap: ReadonlyMap<string, Readonly<TFields>>): Promise<void>;
|
|
43
63
|
cacheDBMissesAsync(keys: readonly string[]): Promise<void>;
|
|
44
64
|
invalidateManyAsync(keys: readonly string[]): Promise<void>;
|
|
45
|
-
|
|
65
|
+
private makeCacheKeyForCacheKeyVersion;
|
|
66
|
+
makeCacheKeyForStorage<TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>, TSerializedLoadValue, TLoadValue extends IEntityLoadValue<TSerializedLoadValue>>(key: TLoadKey, value: TLoadValue): string;
|
|
67
|
+
makeCacheKeysForInvalidation<TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>, TSerializedLoadValue, TLoadValue extends IEntityLoadValue<TSerializedLoadValue>>(key: TLoadKey, value: TLoadValue): readonly string[];
|
|
46
68
|
}
|
|
@@ -3,13 +3,31 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.RedisCacheInvalidationStrategy = void 0;
|
|
6
7
|
const entity_1 = require("@expo/entity");
|
|
7
|
-
const invariant_1 = __importDefault(require("invariant"));
|
|
8
8
|
const RedisCommon_1 = require("./RedisCommon");
|
|
9
9
|
const wrapNativeRedisCallAsync_1 = __importDefault(require("./errors/wrapNativeRedisCallAsync"));
|
|
10
|
+
const getSurroundingCacheKeyVersionsForInvalidation_1 = require("./utils/getSurroundingCacheKeyVersionsForInvalidation");
|
|
10
11
|
// Sentinel value we store in Redis to negatively cache a database miss.
|
|
11
12
|
// The sentinel value is distinct from any (positively) cached value.
|
|
12
13
|
const DOES_NOT_EXIST_REDIS = '';
|
|
14
|
+
/**
|
|
15
|
+
* The strategy for generating the set of cache keys to invalidate in the Redis cache after entity mutation.
|
|
16
|
+
*/
|
|
17
|
+
var RedisCacheInvalidationStrategy;
|
|
18
|
+
(function (RedisCacheInvalidationStrategy) {
|
|
19
|
+
/**
|
|
20
|
+
* Invalidate just the cache key(s) for the current cacheKeyVersion of the entity.
|
|
21
|
+
*/
|
|
22
|
+
RedisCacheInvalidationStrategy["CURRENT_CACHE_KEY_VERSION"] = "current-cache-key-version";
|
|
23
|
+
/**
|
|
24
|
+
* Invalidate the cache key(s) for the current cacheKeyVersion and the surrounding cache key versions
|
|
25
|
+
* (e.g. `1`, `2` and `3` if the current version is `2`). This can be useful for deployment safety, where
|
|
26
|
+
* some machines may be operating on an old version of the code and thus an old cacheKeyVersion, and some the new version.
|
|
27
|
+
* This strategy generates cache keys for both old and potential future new versions.
|
|
28
|
+
*/
|
|
29
|
+
RedisCacheInvalidationStrategy["SURROUNDING_CACHE_KEY_VERSIONS"] = "surrounding-cache-key-versions";
|
|
30
|
+
})(RedisCacheInvalidationStrategy || (exports.RedisCacheInvalidationStrategy = RedisCacheInvalidationStrategy = {}));
|
|
13
31
|
class GenericRedisCacher {
|
|
14
32
|
context;
|
|
15
33
|
entityConfiguration;
|
|
@@ -71,10 +89,23 @@ class GenericRedisCacher {
|
|
|
71
89
|
}
|
|
72
90
|
await (0, wrapNativeRedisCallAsync_1.default)(() => this.context.redisClient.del(...keys));
|
|
73
91
|
}
|
|
74
|
-
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
return this.context.makeKeyFn(this.context.cacheKeyPrefix, this.entityConfiguration.tableName, `v2.${
|
|
92
|
+
makeCacheKeyForCacheKeyVersion(key, value, cacheKeyVersion) {
|
|
93
|
+
const cacheKeyType = key.getLoadMethodType();
|
|
94
|
+
const parts = key.createCacheKeyPartsForLoadValue(this.entityConfiguration, value);
|
|
95
|
+
return this.context.makeKeyFn(this.context.cacheKeyPrefix, cacheKeyType, this.entityConfiguration.tableName, `v2.${cacheKeyVersion}`, ...parts);
|
|
96
|
+
}
|
|
97
|
+
makeCacheKeyForStorage(key, value) {
|
|
98
|
+
return this.makeCacheKeyForCacheKeyVersion(key, value, this.entityConfiguration.cacheKeyVersion);
|
|
99
|
+
}
|
|
100
|
+
makeCacheKeysForInvalidation(key, value) {
|
|
101
|
+
switch (this.context.invalidationStrategy) {
|
|
102
|
+
case RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION:
|
|
103
|
+
return [
|
|
104
|
+
this.makeCacheKeyForCacheKeyVersion(key, value, this.entityConfiguration.cacheKeyVersion),
|
|
105
|
+
];
|
|
106
|
+
case RedisCacheInvalidationStrategy.SURROUNDING_CACHE_KEY_VERSIONS:
|
|
107
|
+
return (0, getSurroundingCacheKeyVersionsForInvalidation_1.getSurroundingCacheKeyVersionsForInvalidation)(this.entityConfiguration.cacheKeyVersion).map((cacheKeyVersion) => this.makeCacheKeyForCacheKeyVersion(key, value, cacheKeyVersion));
|
|
108
|
+
}
|
|
78
109
|
}
|
|
79
110
|
}
|
|
80
111
|
exports.default = GenericRedisCacher;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GenericRedisCacher.js","sourceRoot":"","sources":["../src/GenericRedisCacher.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GenericRedisCacher.js","sourceRoot":"","sources":["../src/GenericRedisCacher.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,5 +3,5 @@ import { GenericRedisCacheContext } from './GenericRedisCacher';
|
|
|
3
3
|
export default class RedisCacheAdapterProvider implements IEntityCacheAdapterProvider {
|
|
4
4
|
private readonly context;
|
|
5
5
|
constructor(context: GenericRedisCacheContext);
|
|
6
|
-
getCacheAdapter<TFields extends Record<string, any
|
|
6
|
+
getCacheAdapter<TFields extends Record<string, any>, TIDField extends keyof TFields>(entityConfiguration: EntityConfiguration<TFields, TIDField>): IEntityCacheAdapter<TFields, TIDField>;
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RedisCacheAdapterProvider.js","sourceRoot":"","sources":["../src/RedisCacheAdapterProvider.ts"],"names":[],"mappings":";;;;;AAAA,yCAKsB;AAEtB,8EAAoF;AAEpF,MAAqB,yBAAyB;IACf;IAA7B,YAA6B,OAAiC;QAAjC,YAAO,GAAP,OAAO,CAA0B;IAAG,CAAC;IAElE,eAAe,CACb,
|
|
1
|
+
{"version":3,"file":"RedisCacheAdapterProvider.js","sourceRoot":"","sources":["../src/RedisCacheAdapterProvider.ts"],"names":[],"mappings":";;;;;AAAA,yCAKsB;AAEtB,8EAAoF;AAEpF,MAAqB,yBAAyB;IACf;IAA7B,YAA6B,OAAiC;QAAjC,YAAO,GAAP,OAAO,CAA0B;IAAG,CAAC;IAElE,eAAe,CACb,mBAA2D;QAE3D,OAAO,IAAI,kCAAyB,CAAC,IAAI,4BAAkB,CAAC,IAAI,CAAC,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC;IAClG,CAAC;CACF;AARD,4CAQC"}
|
package/build/index.d.ts
CHANGED
|
@@ -7,3 +7,4 @@ export * from './GenericRedisCacher';
|
|
|
7
7
|
export { default as RedisCacheAdapterProvider } from './RedisCacheAdapterProvider';
|
|
8
8
|
export * from './RedisCommon';
|
|
9
9
|
export { default as wrapNativeRedisCallAsync } from './errors/wrapNativeRedisCallAsync';
|
|
10
|
+
export * from './utils/getSurroundingCacheKeyVersionsForInvalidation';
|
package/build/index.js
CHANGED
|
@@ -31,4 +31,5 @@ Object.defineProperty(exports, "RedisCacheAdapterProvider", { enumerable: true,
|
|
|
31
31
|
__exportStar(require("./RedisCommon"), exports);
|
|
32
32
|
var wrapNativeRedisCallAsync_1 = require("./errors/wrapNativeRedisCallAsync");
|
|
33
33
|
Object.defineProperty(exports, "wrapNativeRedisCallAsync", { enumerable: true, get: function () { return __importDefault(wrapNativeRedisCallAsync_1).default; } });
|
|
34
|
+
__exportStar(require("./utils/getSurroundingCacheKeyVersionsForInvalidation"), exports);
|
|
34
35
|
//# sourceMappingURL=index.js.map
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,iCAAiC;AACjC;;;GAGG;;;;;;;;;;;;;;;;;;;;AAEH,2DAAqE;AAA5D,yIAAA,OAAO,OAAsB;AACtC,uDAAqC;AACrC,yEAAmF;AAA1E,uJAAA,OAAO,OAA6B;AAC7C,gDAA8B;AAC9B,8EAAwF;AAA/E,qJAAA,OAAO,OAA4B"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA,iCAAiC;AACjC;;;GAGG;;;;;;;;;;;;;;;;;;;;AAEH,2DAAqE;AAA5D,yIAAA,OAAO,OAAsB;AACtC,uDAAqC;AACrC,yEAAmF;AAA1E,uJAAA,OAAO,OAA6B;AAC7C,gDAA8B;AAC9B,8EAAwF;AAA/E,qJAAA,OAAO,OAA4B;AAC5C,wFAAsE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../src/genericrediscacher.ts","../src/rediscacheadapterprovider.ts","../src/rediscommon.ts","../src/index.ts","../src/errors/wrapnativerediscallasync.ts","../src/utils/getsurroundingcachekeyversionsforinvalidation.ts"],"version":"5.8.3"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getSurroundingCacheKeyVersionsForInvalidation(cacheKeyVersion: number): readonly number[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSurroundingCacheKeyVersionsForInvalidation = getSurroundingCacheKeyVersionsForInvalidation;
|
|
4
|
+
function getSurroundingCacheKeyVersionsForInvalidation(cacheKeyVersion) {
|
|
5
|
+
return [
|
|
6
|
+
...(cacheKeyVersion === 0 ? [] : [cacheKeyVersion - 1]),
|
|
7
|
+
cacheKeyVersion,
|
|
8
|
+
cacheKeyVersion + 1,
|
|
9
|
+
];
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=getSurroundingCacheKeyVersionsForInvalidation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"getSurroundingCacheKeyVersionsForInvalidation.js","sourceRoot":"","sources":["../../src/utils/getSurroundingCacheKeyVersionsForInvalidation.ts"],"names":[],"mappings":";;AAAA,sGAQC;AARD,SAAgB,6CAA6C,CAC3D,eAAuB;IAEvB,OAAO;QACL,GAAG,CAAC,eAAe,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC;QACvD,eAAe;QACf,eAAe,GAAG,CAAC;KACpB,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/entity-cache-adapter-redis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "Redis cache adapter for @expo/entity",
|
|
5
5
|
"files": [
|
|
6
6
|
"build",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"types": "build/index.d.ts",
|
|
11
11
|
"scripts": {
|
|
12
12
|
"tsc": "tsc",
|
|
13
|
+
"build": "tsc -b tsconfig.build.json",
|
|
13
14
|
"clean": "rm -rf build coverage coverage-integration",
|
|
14
15
|
"lint": "eslint src",
|
|
15
16
|
"lint-fix": "eslint src --fix",
|
|
@@ -27,26 +28,27 @@
|
|
|
27
28
|
"author": "Expo",
|
|
28
29
|
"license": "MIT",
|
|
29
30
|
"dependencies": {
|
|
30
|
-
"@expo/entity": "^0.
|
|
31
|
+
"@expo/entity": "^0.42.0"
|
|
31
32
|
},
|
|
32
33
|
"peerDependencies": {
|
|
33
34
|
"ioredis": ">=5"
|
|
34
35
|
},
|
|
35
36
|
"devDependencies": {
|
|
36
37
|
"@expo/batcher": "^1.0.0",
|
|
37
|
-
"@
|
|
38
|
+
"@expo/entity-testing-utils": "^0.42.0",
|
|
39
|
+
"@types/jest": "^29.5.14",
|
|
38
40
|
"@types/node": "^20.14.1",
|
|
39
41
|
"ctix": "^2.7.0",
|
|
40
42
|
"eslint": "^8.57.1",
|
|
41
43
|
"eslint-config-universe": "^14.0.0",
|
|
42
44
|
"eslint-plugin-tsdoc": "^0.3.0",
|
|
43
|
-
"ioredis": "^5.
|
|
45
|
+
"ioredis": "^5.6.0",
|
|
44
46
|
"jest": "^29.7.0",
|
|
45
47
|
"prettier": "^3.3.3",
|
|
46
48
|
"prettier-plugin-organize-imports": "^4.1.0",
|
|
47
|
-
"ts-jest": "^29.
|
|
49
|
+
"ts-jest": "^29.3.1",
|
|
48
50
|
"ts-mockito": "^2.6.1",
|
|
49
|
-
"typescript": "^5.
|
|
51
|
+
"typescript": "^5.8.3"
|
|
50
52
|
},
|
|
51
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "8414d96d948882735687da146e84913397cd8368"
|
|
52
54
|
}
|
|
@@ -5,11 +5,13 @@ import {
|
|
|
5
5
|
transformCacheObjectToFields,
|
|
6
6
|
transformFieldsToCacheObject,
|
|
7
7
|
IEntityGenericCacher,
|
|
8
|
+
IEntityLoadKey,
|
|
9
|
+
IEntityLoadValue,
|
|
8
10
|
} from '@expo/entity';
|
|
9
|
-
import invariant from 'invariant';
|
|
10
11
|
|
|
11
12
|
import { redisTransformerMap } from './RedisCommon';
|
|
12
13
|
import wrapNativeRedisCallAsync from './errors/wrapNativeRedisCallAsync';
|
|
14
|
+
import { getSurroundingCacheKeyVersionsForInvalidation } from './utils/getSurroundingCacheKeyVersionsForInvalidation';
|
|
13
15
|
|
|
14
16
|
// Sentinel value we store in Redis to negatively cache a database miss.
|
|
15
17
|
// The sentinel value is distinct from any (positively) cached value.
|
|
@@ -26,6 +28,24 @@ export interface IRedis {
|
|
|
26
28
|
del(...args: [...keys: string[]]): Promise<any>;
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
/**
|
|
32
|
+
* The strategy for generating the set of cache keys to invalidate in the Redis cache after entity mutation.
|
|
33
|
+
*/
|
|
34
|
+
export enum RedisCacheInvalidationStrategy {
|
|
35
|
+
/**
|
|
36
|
+
* Invalidate just the cache key(s) for the current cacheKeyVersion of the entity.
|
|
37
|
+
*/
|
|
38
|
+
CURRENT_CACHE_KEY_VERSION = 'current-cache-key-version',
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Invalidate the cache key(s) for the current cacheKeyVersion and the surrounding cache key versions
|
|
42
|
+
* (e.g. `1`, `2` and `3` if the current version is `2`). This can be useful for deployment safety, where
|
|
43
|
+
* some machines may be operating on an old version of the code and thus an old cacheKeyVersion, and some the new version.
|
|
44
|
+
* This strategy generates cache keys for both old and potential future new versions.
|
|
45
|
+
*/
|
|
46
|
+
SURROUNDING_CACHE_KEY_VERSIONS = 'surrounding-cache-key-versions',
|
|
47
|
+
}
|
|
48
|
+
|
|
29
49
|
export interface GenericRedisCacheContext {
|
|
30
50
|
/**
|
|
31
51
|
* Instance of ioredis.Redis
|
|
@@ -55,14 +75,21 @@ export interface GenericRedisCacheContext {
|
|
|
55
75
|
* this TTL will be assumed not present in the database (unless invalidated).
|
|
56
76
|
*/
|
|
57
77
|
ttlSecondsNegative: number;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Invalidation strategy for the cache.
|
|
81
|
+
*/
|
|
82
|
+
invalidationStrategy: RedisCacheInvalidationStrategy;
|
|
58
83
|
}
|
|
59
84
|
|
|
60
|
-
export default class GenericRedisCacher<
|
|
61
|
-
|
|
85
|
+
export default class GenericRedisCacher<
|
|
86
|
+
TFields extends Record<string, any>,
|
|
87
|
+
TIDField extends keyof TFields,
|
|
88
|
+
> implements IEntityGenericCacher<TFields, TIDField>
|
|
62
89
|
{
|
|
63
90
|
constructor(
|
|
64
91
|
private readonly context: GenericRedisCacheContext,
|
|
65
|
-
private readonly entityConfiguration: EntityConfiguration<TFields>,
|
|
92
|
+
private readonly entityConfiguration: EntityConfiguration<TFields, TIDField>,
|
|
66
93
|
) {}
|
|
67
94
|
|
|
68
95
|
public async loadManyAsync(
|
|
@@ -147,18 +174,50 @@ export default class GenericRedisCacher<TFields extends Record<string, any>>
|
|
|
147
174
|
await wrapNativeRedisCallAsync(() => this.context.redisClient.del(...keys));
|
|
148
175
|
}
|
|
149
176
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
177
|
+
private makeCacheKeyForCacheKeyVersion<
|
|
178
|
+
TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>,
|
|
179
|
+
TSerializedLoadValue,
|
|
180
|
+
TLoadValue extends IEntityLoadValue<TSerializedLoadValue>,
|
|
181
|
+
>(key: TLoadKey, value: TLoadValue, cacheKeyVersion: number): string {
|
|
182
|
+
const cacheKeyType = key.getLoadMethodType();
|
|
183
|
+
const parts = key.createCacheKeyPartsForLoadValue(this.entityConfiguration, value);
|
|
156
184
|
return this.context.makeKeyFn(
|
|
157
185
|
this.context.cacheKeyPrefix,
|
|
186
|
+
cacheKeyType,
|
|
158
187
|
this.entityConfiguration.tableName,
|
|
159
|
-
`v2.${
|
|
160
|
-
|
|
161
|
-
|
|
188
|
+
`v2.${cacheKeyVersion}`,
|
|
189
|
+
...parts,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
public makeCacheKeyForStorage<
|
|
194
|
+
TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>,
|
|
195
|
+
TSerializedLoadValue,
|
|
196
|
+
TLoadValue extends IEntityLoadValue<TSerializedLoadValue>,
|
|
197
|
+
>(key: TLoadKey, value: TLoadValue): string {
|
|
198
|
+
return this.makeCacheKeyForCacheKeyVersion(
|
|
199
|
+
key,
|
|
200
|
+
value,
|
|
201
|
+
this.entityConfiguration.cacheKeyVersion,
|
|
162
202
|
);
|
|
163
203
|
}
|
|
204
|
+
|
|
205
|
+
public makeCacheKeysForInvalidation<
|
|
206
|
+
TLoadKey extends IEntityLoadKey<TFields, TIDField, TSerializedLoadValue, TLoadValue>,
|
|
207
|
+
TSerializedLoadValue,
|
|
208
|
+
TLoadValue extends IEntityLoadValue<TSerializedLoadValue>,
|
|
209
|
+
>(key: TLoadKey, value: TLoadValue): readonly string[] {
|
|
210
|
+
switch (this.context.invalidationStrategy) {
|
|
211
|
+
case RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION:
|
|
212
|
+
return [
|
|
213
|
+
this.makeCacheKeyForCacheKeyVersion(key, value, this.entityConfiguration.cacheKeyVersion),
|
|
214
|
+
];
|
|
215
|
+
case RedisCacheInvalidationStrategy.SURROUNDING_CACHE_KEY_VERSIONS:
|
|
216
|
+
return getSurroundingCacheKeyVersionsForInvalidation(
|
|
217
|
+
this.entityConfiguration.cacheKeyVersion,
|
|
218
|
+
).map((cacheKeyVersion) =>
|
|
219
|
+
this.makeCacheKeyForCacheKeyVersion(key, value, cacheKeyVersion),
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
164
223
|
}
|
|
@@ -10,9 +10,9 @@ import GenericRedisCacher, { GenericRedisCacheContext } from './GenericRedisCach
|
|
|
10
10
|
export default class RedisCacheAdapterProvider implements IEntityCacheAdapterProvider {
|
|
11
11
|
constructor(private readonly context: GenericRedisCacheContext) {}
|
|
12
12
|
|
|
13
|
-
getCacheAdapter<TFields extends Record<string, any
|
|
14
|
-
entityConfiguration: EntityConfiguration<TFields>,
|
|
15
|
-
): IEntityCacheAdapter<TFields> {
|
|
13
|
+
getCacheAdapter<TFields extends Record<string, any>, TIDField extends keyof TFields>(
|
|
14
|
+
entityConfiguration: EntityConfiguration<TFields, TIDField>,
|
|
15
|
+
): IEntityCacheAdapter<TFields, TIDField> {
|
|
16
16
|
return new GenericEntityCacheAdapter(new GenericRedisCacher(this.context, entityConfiguration));
|
|
17
17
|
}
|
|
18
18
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { Batcher } from '@expo/batcher';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
IEntityGenericCacher,
|
|
4
|
+
SingleFieldHolder,
|
|
5
|
+
SingleFieldValueHolder,
|
|
6
|
+
ViewerContext,
|
|
7
|
+
zipToMap,
|
|
8
|
+
} from '@expo/entity';
|
|
3
9
|
import invariant from 'invariant';
|
|
4
10
|
import Redis from 'ioredis';
|
|
5
11
|
import nullthrows from 'nullthrows';
|
|
@@ -10,9 +16,10 @@ import GenericRedisCacher, {
|
|
|
10
16
|
IRedis,
|
|
11
17
|
IRedisTransaction,
|
|
12
18
|
GenericRedisCacheContext,
|
|
19
|
+
RedisCacheInvalidationStrategy,
|
|
13
20
|
} from '../GenericRedisCacher';
|
|
14
|
-
import RedisTestEntity from '../
|
|
15
|
-
import { createRedisIntegrationTestEntityCompanionProvider } from '../
|
|
21
|
+
import RedisTestEntity, { RedisTestEntityFields } from '../__testfixtures__/RedisTestEntity';
|
|
22
|
+
import { createRedisIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createRedisIntegrationTestEntityCompanionProvider';
|
|
16
23
|
|
|
17
24
|
class BatchedRedis implements IRedis {
|
|
18
25
|
private readonly mgetBatcher = new Batcher<string[], (string | null)[]>(
|
|
@@ -78,6 +85,7 @@ describe(GenericRedisCacher, () => {
|
|
|
78
85
|
cacheKeyPrefix: 'test-',
|
|
79
86
|
ttlSecondsPositive: 86400, // 1 day
|
|
80
87
|
ttlSecondsNegative: 600, // 10 minutes
|
|
88
|
+
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
|
|
81
89
|
};
|
|
82
90
|
});
|
|
83
91
|
|
|
@@ -97,11 +105,12 @@ describe(GenericRedisCacher, () => {
|
|
|
97
105
|
|
|
98
106
|
const mgetSpy = jest.spyOn(redis, 'mget');
|
|
99
107
|
|
|
100
|
-
const genericCacher =
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
const genericCacher = viewerContext.entityCompanionProvider.getCompanionForEntity(
|
|
109
|
+
RedisTestEntity,
|
|
110
|
+
)['tableDataCoordinator']['cacheAdapter']['genericCacher'] as IEntityGenericCacher<
|
|
111
|
+
RedisTestEntityFields,
|
|
112
|
+
'id'
|
|
113
|
+
>;
|
|
105
114
|
|
|
106
115
|
const entity1Created = await RedisTestEntity.creator(viewerContext)
|
|
107
116
|
.setField('name', 'blah')
|
|
@@ -132,7 +141,10 @@ describe(GenericRedisCacher, () => {
|
|
|
132
141
|
expect(entity1.getID()).toEqual(entity2.getID());
|
|
133
142
|
expect(entity2.getID()).toEqual(nullthrows(entity3).getID());
|
|
134
143
|
|
|
135
|
-
const cacheKeyEntity1 =
|
|
144
|
+
const cacheKeyEntity1 = genericCacher.makeCacheKeyForStorage(
|
|
145
|
+
new SingleFieldHolder('id'),
|
|
146
|
+
new SingleFieldValueHolder(entity1Created.getID()),
|
|
147
|
+
);
|
|
136
148
|
const cachedJSON = await redis.get(cacheKeyEntity1);
|
|
137
149
|
const cachedValue = JSON.parse(cachedJSON!);
|
|
138
150
|
expect(cachedValue).toMatchObject({
|
|
@@ -140,7 +152,10 @@ describe(GenericRedisCacher, () => {
|
|
|
140
152
|
name: 'blah',
|
|
141
153
|
});
|
|
142
154
|
|
|
143
|
-
const cacheKeyEntity1NameField =
|
|
155
|
+
const cacheKeyEntity1NameField = genericCacher.makeCacheKeyForStorage(
|
|
156
|
+
new SingleFieldHolder('name'),
|
|
157
|
+
new SingleFieldValueHolder(entity1Created.getField('name')),
|
|
158
|
+
);
|
|
144
159
|
await RedisTestEntity.loader(viewerContext).loadByFieldEqualingAsync(
|
|
145
160
|
'name',
|
|
146
161
|
entity1Created.getField('name'),
|
|
@@ -154,7 +169,10 @@ describe(GenericRedisCacher, () => {
|
|
|
154
169
|
nonExistentId,
|
|
155
170
|
);
|
|
156
171
|
expect(entityNonExistentResult.ok).toBe(false);
|
|
157
|
-
const cacheKeyNonExistent =
|
|
172
|
+
const cacheKeyNonExistent = genericCacher.makeCacheKeyForStorage(
|
|
173
|
+
new SingleFieldHolder('id'),
|
|
174
|
+
new SingleFieldValueHolder(nonExistentId),
|
|
175
|
+
);
|
|
158
176
|
const nonExistentCachedValue = await redis.get(cacheKeyNonExistent);
|
|
159
177
|
expect(nonExistentCachedValue).toEqual('');
|
|
160
178
|
// load again through entities framework to ensure it reads negative result
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CompositeFieldHolder,
|
|
3
|
+
CompositeFieldValueHolder,
|
|
4
|
+
IEntityGenericCacher,
|
|
5
|
+
SingleFieldHolder,
|
|
6
|
+
SingleFieldValueHolder,
|
|
7
|
+
ViewerContext,
|
|
8
|
+
} from '@expo/entity';
|
|
2
9
|
import { enforceAsyncResult } from '@expo/results';
|
|
3
10
|
import Redis from 'ioredis';
|
|
4
11
|
import { URL } from 'url';
|
|
5
12
|
import { v4 as uuidv4 } from 'uuid';
|
|
6
13
|
|
|
7
|
-
import GenericRedisCacher, {
|
|
8
|
-
|
|
9
|
-
|
|
14
|
+
import GenericRedisCacher, {
|
|
15
|
+
GenericRedisCacheContext,
|
|
16
|
+
RedisCacheInvalidationStrategy,
|
|
17
|
+
} from '../GenericRedisCacher';
|
|
18
|
+
import RedisTestEntity, { RedisTestEntityFields } from '../__testfixtures__/RedisTestEntity';
|
|
19
|
+
import { createRedisIntegrationTestEntityCompanionProvider } from '../__testfixtures__/createRedisIntegrationTestEntityCompanionProvider';
|
|
10
20
|
|
|
11
21
|
class TestViewerContext extends ViewerContext {}
|
|
12
22
|
|
|
@@ -26,6 +36,7 @@ describe(GenericRedisCacher, () => {
|
|
|
26
36
|
cacheKeyPrefix: 'test-',
|
|
27
37
|
ttlSecondsPositive: 86400, // 1 day
|
|
28
38
|
ttlSecondsNegative: 600, // 10 minutes
|
|
39
|
+
invalidationStrategy: RedisCacheInvalidationStrategy.CURRENT_CACHE_KEY_VERSION,
|
|
29
40
|
};
|
|
30
41
|
});
|
|
31
42
|
|
|
@@ -40,11 +51,12 @@ describe(GenericRedisCacher, () => {
|
|
|
40
51
|
const viewerContext = new TestViewerContext(
|
|
41
52
|
createRedisIntegrationTestEntityCompanionProvider(genericRedisCacheContext),
|
|
42
53
|
);
|
|
43
|
-
const genericCacher =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
54
|
+
const genericCacher = viewerContext.entityCompanionProvider.getCompanionForEntity(
|
|
55
|
+
RedisTestEntity,
|
|
56
|
+
)['tableDataCoordinator']['cacheAdapter']['genericCacher'] as IEntityGenericCacher<
|
|
57
|
+
RedisTestEntityFields,
|
|
58
|
+
'id'
|
|
59
|
+
>;
|
|
48
60
|
|
|
49
61
|
const entity1Created = await RedisTestEntity.creator(viewerContext)
|
|
50
62
|
.setField('name', 'blah')
|
|
@@ -55,15 +67,35 @@ describe(GenericRedisCacher, () => {
|
|
|
55
67
|
entity1Created.getID(),
|
|
56
68
|
);
|
|
57
69
|
|
|
58
|
-
const
|
|
59
|
-
|
|
70
|
+
const cachedSingleJSON = await (genericRedisCacheContext.redisClient as Redis).get(
|
|
71
|
+
genericCacher.makeCacheKeyForStorage(
|
|
72
|
+
new SingleFieldHolder('id'),
|
|
73
|
+
new SingleFieldValueHolder(entity1.getID()),
|
|
74
|
+
),
|
|
60
75
|
);
|
|
61
|
-
const
|
|
62
|
-
expect(
|
|
76
|
+
const cachedSingleValue = JSON.parse(cachedSingleJSON!);
|
|
77
|
+
expect(cachedSingleValue).toMatchObject({
|
|
63
78
|
id: entity1.getID(),
|
|
64
79
|
name: 'blah',
|
|
65
80
|
});
|
|
66
81
|
|
|
82
|
+
// loading an entity by composite field should put it in cache
|
|
83
|
+
const entity2 = await RedisTestEntity.loader(viewerContext).loadByCompositeFieldEqualingAsync(
|
|
84
|
+
['name', 'id'],
|
|
85
|
+
{ name: 'blah', id: entity1.getID() },
|
|
86
|
+
);
|
|
87
|
+
const cachedCompositeJSON = await (genericRedisCacheContext.redisClient as Redis).get(
|
|
88
|
+
genericCacher.makeCacheKeyForStorage(
|
|
89
|
+
new CompositeFieldHolder<RedisTestEntityFields, 'id'>(['id', 'name']),
|
|
90
|
+
new CompositeFieldValueHolder({ id: entity2!.getID(), name: 'blah' }),
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
const cachedCompositeValue = JSON.parse(cachedCompositeJSON!);
|
|
94
|
+
expect(cachedCompositeValue).toMatchObject({
|
|
95
|
+
id: entity2!.getID(),
|
|
96
|
+
name: 'blah',
|
|
97
|
+
});
|
|
98
|
+
|
|
67
99
|
// simulate non existent db fetch, should write negative result ('') to cache
|
|
68
100
|
const nonExistentId = uuidv4();
|
|
69
101
|
|
|
@@ -72,25 +104,58 @@ describe(GenericRedisCacher, () => {
|
|
|
72
104
|
nonExistentId,
|
|
73
105
|
);
|
|
74
106
|
expect(entityNonExistentResult.ok).toBe(false);
|
|
75
|
-
|
|
76
107
|
const nonExistentCachedValue = await (genericRedisCacheContext.redisClient as Redis).get(
|
|
77
|
-
|
|
108
|
+
genericCacher.makeCacheKeyForStorage(
|
|
109
|
+
new SingleFieldHolder('id'),
|
|
110
|
+
new SingleFieldValueHolder(nonExistentId),
|
|
111
|
+
),
|
|
78
112
|
);
|
|
79
113
|
expect(nonExistentCachedValue).toEqual('');
|
|
80
114
|
|
|
115
|
+
const entityNonExistentCompositeResult = await RedisTestEntity.loader(
|
|
116
|
+
viewerContext,
|
|
117
|
+
).loadByCompositeFieldEqualingAsync(['name', 'id'], { name: 'blah', id: nonExistentId });
|
|
118
|
+
expect(entityNonExistentCompositeResult).toBe(null);
|
|
119
|
+
const nonExistentCompositeCachedValue = await (
|
|
120
|
+
genericRedisCacheContext.redisClient as Redis
|
|
121
|
+
).get(
|
|
122
|
+
genericCacher.makeCacheKeyForStorage(
|
|
123
|
+
new CompositeFieldHolder<RedisTestEntityFields, 'id'>(['id', 'name']),
|
|
124
|
+
new CompositeFieldValueHolder({ id: nonExistentId, name: 'blah' }),
|
|
125
|
+
),
|
|
126
|
+
);
|
|
127
|
+
expect(nonExistentCompositeCachedValue).toEqual('');
|
|
128
|
+
|
|
81
129
|
// load again through entities framework to ensure it reads negative result
|
|
82
130
|
const entityNonExistentResult2 =
|
|
83
131
|
await RedisTestEntity.loaderWithAuthorizationResults(viewerContext).loadByIDAsync(
|
|
84
132
|
nonExistentId,
|
|
85
133
|
);
|
|
86
134
|
expect(entityNonExistentResult2.ok).toBe(false);
|
|
135
|
+
const entityNonExistentCompositeResult2 = await RedisTestEntity.loader(
|
|
136
|
+
viewerContext,
|
|
137
|
+
).loadByCompositeFieldEqualingAsync(['name', 'id'], { name: 'blah', id: nonExistentId });
|
|
138
|
+
expect(entityNonExistentCompositeResult2).toBe(null);
|
|
87
139
|
|
|
88
140
|
// invalidate from cache to ensure it invalidates correctly
|
|
89
141
|
await RedisTestEntity.loaderUtils(viewerContext).invalidateFieldsAsync(entity1.getAllFields());
|
|
90
|
-
const
|
|
91
|
-
|
|
142
|
+
const cachedValueNullKeys = genericCacher.makeCacheKeysForInvalidation(
|
|
143
|
+
new SingleFieldHolder('id'),
|
|
144
|
+
new SingleFieldValueHolder(entity1.getID()),
|
|
145
|
+
);
|
|
146
|
+
const cachedValueNull = await (genericRedisCacheContext.redisClient as Redis).mget(
|
|
147
|
+
...cachedValueNullKeys,
|
|
148
|
+
);
|
|
149
|
+
expect(cachedValueNull.every((c) => c === null)).toBe(true);
|
|
150
|
+
|
|
151
|
+
const cachedValueNullCompositeKeys = genericCacher.makeCacheKeysForInvalidation(
|
|
152
|
+
new CompositeFieldHolder<RedisTestEntityFields, 'id'>(['id', 'name']),
|
|
153
|
+
new CompositeFieldValueHolder({ id: entity1.getID(), name: 'blah' }),
|
|
154
|
+
);
|
|
155
|
+
const cachedValueNullComposite = await (genericRedisCacheContext.redisClient as Redis).mget(
|
|
156
|
+
...cachedValueNullCompositeKeys,
|
|
92
157
|
);
|
|
93
|
-
expect(
|
|
158
|
+
expect(cachedValueNullComposite.every((c) => c === null)).toBe(true);
|
|
94
159
|
});
|
|
95
160
|
|
|
96
161
|
it('caches and restores date fields', async () => {
|