@expo/entity-secondary-cache-redis 0.22.0 → 0.25.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.
|
@@ -1,33 +1,8 @@
|
|
|
1
|
-
import { EntityConfiguration,
|
|
1
|
+
import { EntityConfiguration, GenericSecondaryEntityCache } from '@expo/entity';
|
|
2
2
|
import { GenericRedisCacheContext } from '@expo/entity-cache-adapter-redis';
|
|
3
3
|
/**
|
|
4
|
-
* A
|
|
5
|
-
* single entity load. One common way this may be used is to add a second layer of caching in a hot path that makes
|
|
6
|
-
* a call to {@link EntityLoader.loadManyByFieldEqualityConjunctionAsync} is guaranteed to return at most one entity.
|
|
4
|
+
* A redis {@link GenericSecondaryEntityCache}.
|
|
7
5
|
*/
|
|
8
|
-
export default class RedisSecondaryEntityCache<TFields, TLoadParams>
|
|
9
|
-
private readonly entityConfiguration;
|
|
10
|
-
private readonly constructRedisKey;
|
|
11
|
-
private readonly genericRedisCacher;
|
|
6
|
+
export default class RedisSecondaryEntityCache<TFields, TLoadParams> extends GenericSecondaryEntityCache<TFields, TLoadParams> {
|
|
12
7
|
constructor(entityConfiguration: EntityConfiguration<TFields>, genericRedisCacheContext: GenericRedisCacheContext, constructRedisKey: (params: Readonly<TLoadParams>) => string);
|
|
13
|
-
/**
|
|
14
|
-
* Read-through cache function. Steps:
|
|
15
|
-
*
|
|
16
|
-
* 1. Check for cached objects
|
|
17
|
-
* 2. Query the fetcher for loadParams not in the cache
|
|
18
|
-
* 3. Cache the results from the fetcher
|
|
19
|
-
* 4. Negatively cache anything missing from the fetcher
|
|
20
|
-
* 5. Return the full set of data for the query.
|
|
21
|
-
*
|
|
22
|
-
* @param loadParamsArray - array of loadParams to load from the cache
|
|
23
|
-
* @param fetcher - closure used to provide underlying data source objects for loadParams
|
|
24
|
-
* @returns map from loadParams to the entity field object
|
|
25
|
-
*/
|
|
26
|
-
loadManyThroughAsync(loadParamsArray: readonly Readonly<TLoadParams>[], fetcher: (fetcherLoadParamsArray: readonly Readonly<TLoadParams>[]) => Promise<ReadonlyMap<Readonly<TLoadParams>, Readonly<TFields> | null>>): Promise<ReadonlyMap<Readonly<TLoadParams>, Readonly<TFields> | null>>;
|
|
27
|
-
/**
|
|
28
|
-
* Invalidate the cache for objects cached by constructRedisKey(loadParams).
|
|
29
|
-
*
|
|
30
|
-
* @param loadParamsArray - load params to invalidate
|
|
31
|
-
*/
|
|
32
|
-
invalidateManyAsync(loadParamsArray: readonly Readonly<TLoadParams>[]): Promise<void>;
|
|
33
8
|
}
|
|
@@ -1,86 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
const entity_1 = require("@expo/entity");
|
|
7
4
|
const entity_cache_adapter_redis_1 = require("@expo/entity-cache-adapter-redis");
|
|
8
|
-
const invariant_1 = __importDefault(require("invariant"));
|
|
9
5
|
/**
|
|
10
|
-
* A
|
|
11
|
-
* single entity load. One common way this may be used is to add a second layer of caching in a hot path that makes
|
|
12
|
-
* a call to {@link EntityLoader.loadManyByFieldEqualityConjunctionAsync} is guaranteed to return at most one entity.
|
|
6
|
+
* A redis {@link GenericSecondaryEntityCache}.
|
|
13
7
|
*/
|
|
14
|
-
class RedisSecondaryEntityCache {
|
|
8
|
+
class RedisSecondaryEntityCache extends entity_1.GenericSecondaryEntityCache {
|
|
15
9
|
constructor(entityConfiguration, genericRedisCacheContext, constructRedisKey) {
|
|
16
|
-
|
|
17
|
-
this.constructRedisKey = constructRedisKey;
|
|
18
|
-
this.genericRedisCacher = new entity_cache_adapter_redis_1.GenericRedisCacher(genericRedisCacheContext);
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Read-through cache function. Steps:
|
|
22
|
-
*
|
|
23
|
-
* 1. Check for cached objects
|
|
24
|
-
* 2. Query the fetcher for loadParams not in the cache
|
|
25
|
-
* 3. Cache the results from the fetcher
|
|
26
|
-
* 4. Negatively cache anything missing from the fetcher
|
|
27
|
-
* 5. Return the full set of data for the query.
|
|
28
|
-
*
|
|
29
|
-
* @param loadParamsArray - array of loadParams to load from the cache
|
|
30
|
-
* @param fetcher - closure used to provide underlying data source objects for loadParams
|
|
31
|
-
* @returns map from loadParams to the entity field object
|
|
32
|
-
*/
|
|
33
|
-
async loadManyThroughAsync(loadParamsArray, fetcher) {
|
|
34
|
-
const redisKeys = loadParamsArray.map(this.constructRedisKey);
|
|
35
|
-
const redisKeyToLoadParamsMap = (0, entity_1.zipToMap)(redisKeys, loadParamsArray);
|
|
36
|
-
const cacheLoadResults = await this.genericRedisCacher.loadManyAsync(redisKeys);
|
|
37
|
-
(0, invariant_1.default)(cacheLoadResults.size === loadParamsArray.length, `${this.constructor.name} loadMany should return a result for each key`);
|
|
38
|
-
const redisKeysToFetch = Array.from((0, entity_1.filterMap)(cacheLoadResults, (cacheLoadResult) => cacheLoadResult.status === entity_1.CacheStatus.MISS).keys());
|
|
39
|
-
// put transformed cache hits in result map
|
|
40
|
-
const results = new Map();
|
|
41
|
-
cacheLoadResults.forEach((cacheLoadResult, redisKey) => {
|
|
42
|
-
if (cacheLoadResult.status === entity_1.CacheStatus.HIT) {
|
|
43
|
-
const loadParams = redisKeyToLoadParamsMap.get(redisKey);
|
|
44
|
-
(0, invariant_1.default)(loadParams !== undefined, 'load params should be in redis key map');
|
|
45
|
-
results.set(loadParams, (0, entity_1.transformCacheObjectToFields)(this.entityConfiguration, entity_cache_adapter_redis_1.redisTransformerMap, cacheLoadResult.item));
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
// fetch any misses from DB, add DB objects to results, cache DB results, inform cache of any missing DB results
|
|
49
|
-
if (redisKeysToFetch.length > 0) {
|
|
50
|
-
const loadParamsToFetch = redisKeysToFetch.map((redisKey) => {
|
|
51
|
-
const loadParams = redisKeyToLoadParamsMap.get(redisKey);
|
|
52
|
-
(0, invariant_1.default)(loadParams !== undefined, 'load params should be in redis key map');
|
|
53
|
-
return loadParams;
|
|
54
|
-
});
|
|
55
|
-
const fetchResults = await fetcher(loadParamsToFetch);
|
|
56
|
-
const fetchMisses = loadParamsToFetch.filter((loadParams) => {
|
|
57
|
-
// all values of fetchResults should be field objects or undefined
|
|
58
|
-
return !fetchResults.get(loadParams);
|
|
59
|
-
});
|
|
60
|
-
for (const fetchMiss of fetchMisses) {
|
|
61
|
-
results.set(fetchMiss, null);
|
|
62
|
-
}
|
|
63
|
-
const objectsToCache = new Map();
|
|
64
|
-
for (const [loadParams, object] of fetchResults.entries()) {
|
|
65
|
-
if (object) {
|
|
66
|
-
objectsToCache.set(this.constructRedisKey(loadParams), (0, entity_1.transformFieldsToCacheObject)(this.entityConfiguration, entity_cache_adapter_redis_1.redisTransformerMap, object));
|
|
67
|
-
results.set(loadParams, object);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
await Promise.all([
|
|
71
|
-
this.genericRedisCacher.cacheManyAsync(objectsToCache),
|
|
72
|
-
this.genericRedisCacher.cacheDBMissesAsync(fetchMisses.map(this.constructRedisKey)),
|
|
73
|
-
]);
|
|
74
|
-
}
|
|
75
|
-
return results;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Invalidate the cache for objects cached by constructRedisKey(loadParams).
|
|
79
|
-
*
|
|
80
|
-
* @param loadParamsArray - load params to invalidate
|
|
81
|
-
*/
|
|
82
|
-
async invalidateManyAsync(loadParamsArray) {
|
|
83
|
-
await this.genericRedisCacher.invalidateManyAsync(loadParamsArray.map(this.constructRedisKey));
|
|
10
|
+
super(new entity_cache_adapter_redis_1.GenericRedisCacher(genericRedisCacheContext, entityConfiguration), constructRedisKey);
|
|
84
11
|
}
|
|
85
12
|
}
|
|
86
13
|
exports.default = RedisSecondaryEntityCache;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RedisSecondaryEntityCache.js","sourceRoot":"","sources":["../src/RedisSecondaryEntityCache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RedisSecondaryEntityCache.js","sourceRoot":"","sources":["../src/RedisSecondaryEntityCache.ts"],"names":[],"mappings":";;AAAA,yCAAgF;AAChF,iFAAgG;AAEhG;;GAEG;AACH,MAAqB,yBAGnB,SAAQ,oCAAiD;IACzD,YACE,mBAAiD,EACjD,wBAAkD,EAClD,iBAA4D;QAE5D,KAAK,CAAC,IAAI,+CAAkB,CAAC,wBAAwB,EAAE,mBAAmB,CAAC,EAAE,iBAAiB,CAAC,CAAC;IAClG,CAAC;CACF;AAXD,4CAWC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@expo/entity-secondary-cache-redis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0",
|
|
4
4
|
"description": "Redis secondary cache for @expo/entity",
|
|
5
5
|
"files": [
|
|
6
6
|
"build",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"ioredis": "^4.17.3"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
|
-
"@expo/entity": "^0.
|
|
38
|
-
"@expo/entity-cache-adapter-redis": "^0.
|
|
37
|
+
"@expo/entity": "^0.25.0",
|
|
38
|
+
"@expo/entity-cache-adapter-redis": "^0.25.0",
|
|
39
39
|
"nullthrows": "^1.1.1"
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -1,136 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
EntityConfiguration,
|
|
4
|
-
filterMap,
|
|
5
|
-
ISecondaryEntityCache,
|
|
6
|
-
transformCacheObjectToFields,
|
|
7
|
-
transformFieldsToCacheObject,
|
|
8
|
-
zipToMap,
|
|
9
|
-
} from '@expo/entity';
|
|
10
|
-
import {
|
|
11
|
-
GenericRedisCacheContext,
|
|
12
|
-
GenericRedisCacher,
|
|
13
|
-
redisTransformerMap,
|
|
14
|
-
} from '@expo/entity-cache-adapter-redis';
|
|
15
|
-
import invariant from 'invariant';
|
|
1
|
+
import { EntityConfiguration, GenericSecondaryEntityCache } from '@expo/entity';
|
|
2
|
+
import { GenericRedisCacheContext, GenericRedisCacher } from '@expo/entity-cache-adapter-redis';
|
|
16
3
|
|
|
17
4
|
/**
|
|
18
|
-
* A
|
|
19
|
-
* single entity load. One common way this may be used is to add a second layer of caching in a hot path that makes
|
|
20
|
-
* a call to {@link EntityLoader.loadManyByFieldEqualityConjunctionAsync} is guaranteed to return at most one entity.
|
|
5
|
+
* A redis {@link GenericSecondaryEntityCache}.
|
|
21
6
|
*/
|
|
22
|
-
export default class RedisSecondaryEntityCache<
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
7
|
+
export default class RedisSecondaryEntityCache<
|
|
8
|
+
TFields,
|
|
9
|
+
TLoadParams
|
|
10
|
+
> extends GenericSecondaryEntityCache<TFields, TLoadParams> {
|
|
27
11
|
constructor(
|
|
28
|
-
|
|
12
|
+
entityConfiguration: EntityConfiguration<TFields>,
|
|
29
13
|
genericRedisCacheContext: GenericRedisCacheContext,
|
|
30
|
-
|
|
14
|
+
constructRedisKey: (params: Readonly<TLoadParams>) => string
|
|
31
15
|
) {
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Read-through cache function. Steps:
|
|
37
|
-
*
|
|
38
|
-
* 1. Check for cached objects
|
|
39
|
-
* 2. Query the fetcher for loadParams not in the cache
|
|
40
|
-
* 3. Cache the results from the fetcher
|
|
41
|
-
* 4. Negatively cache anything missing from the fetcher
|
|
42
|
-
* 5. Return the full set of data for the query.
|
|
43
|
-
*
|
|
44
|
-
* @param loadParamsArray - array of loadParams to load from the cache
|
|
45
|
-
* @param fetcher - closure used to provide underlying data source objects for loadParams
|
|
46
|
-
* @returns map from loadParams to the entity field object
|
|
47
|
-
*/
|
|
48
|
-
public async loadManyThroughAsync(
|
|
49
|
-
loadParamsArray: readonly Readonly<TLoadParams>[],
|
|
50
|
-
fetcher: (
|
|
51
|
-
fetcherLoadParamsArray: readonly Readonly<TLoadParams>[]
|
|
52
|
-
) => Promise<ReadonlyMap<Readonly<TLoadParams>, Readonly<TFields> | null>>
|
|
53
|
-
): Promise<ReadonlyMap<Readonly<TLoadParams>, Readonly<TFields> | null>> {
|
|
54
|
-
const redisKeys = loadParamsArray.map(this.constructRedisKey);
|
|
55
|
-
const redisKeyToLoadParamsMap = zipToMap(redisKeys, loadParamsArray);
|
|
56
|
-
|
|
57
|
-
const cacheLoadResults = await this.genericRedisCacher.loadManyAsync(redisKeys);
|
|
58
|
-
|
|
59
|
-
invariant(
|
|
60
|
-
cacheLoadResults.size === loadParamsArray.length,
|
|
61
|
-
`${this.constructor.name} loadMany should return a result for each key`
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
const redisKeysToFetch = Array.from(
|
|
65
|
-
filterMap(
|
|
66
|
-
cacheLoadResults,
|
|
67
|
-
(cacheLoadResult) => cacheLoadResult.status === CacheStatus.MISS
|
|
68
|
-
).keys()
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
// put transformed cache hits in result map
|
|
72
|
-
const results: Map<Readonly<TLoadParams>, Readonly<TFields> | null> = new Map();
|
|
73
|
-
cacheLoadResults.forEach((cacheLoadResult, redisKey) => {
|
|
74
|
-
if (cacheLoadResult.status === CacheStatus.HIT) {
|
|
75
|
-
const loadParams = redisKeyToLoadParamsMap.get(redisKey);
|
|
76
|
-
invariant(loadParams !== undefined, 'load params should be in redis key map');
|
|
77
|
-
results.set(
|
|
78
|
-
loadParams,
|
|
79
|
-
transformCacheObjectToFields(
|
|
80
|
-
this.entityConfiguration,
|
|
81
|
-
redisTransformerMap,
|
|
82
|
-
cacheLoadResult.item
|
|
83
|
-
)
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// fetch any misses from DB, add DB objects to results, cache DB results, inform cache of any missing DB results
|
|
89
|
-
if (redisKeysToFetch.length > 0) {
|
|
90
|
-
const loadParamsToFetch = redisKeysToFetch.map((redisKey) => {
|
|
91
|
-
const loadParams = redisKeyToLoadParamsMap.get(redisKey);
|
|
92
|
-
invariant(loadParams !== undefined, 'load params should be in redis key map');
|
|
93
|
-
return loadParams;
|
|
94
|
-
});
|
|
95
|
-
const fetchResults = await fetcher(loadParamsToFetch);
|
|
96
|
-
|
|
97
|
-
const fetchMisses = loadParamsToFetch.filter((loadParams) => {
|
|
98
|
-
// all values of fetchResults should be field objects or undefined
|
|
99
|
-
return !fetchResults.get(loadParams);
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
for (const fetchMiss of fetchMisses) {
|
|
103
|
-
results.set(fetchMiss, null);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const objectsToCache: Map<string, object> = new Map();
|
|
107
|
-
for (const [loadParams, object] of fetchResults.entries()) {
|
|
108
|
-
if (object) {
|
|
109
|
-
objectsToCache.set(
|
|
110
|
-
this.constructRedisKey(loadParams),
|
|
111
|
-
transformFieldsToCacheObject(this.entityConfiguration, redisTransformerMap, object)
|
|
112
|
-
);
|
|
113
|
-
results.set(loadParams, object);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
await Promise.all([
|
|
118
|
-
this.genericRedisCacher.cacheManyAsync(objectsToCache),
|
|
119
|
-
this.genericRedisCacher.cacheDBMissesAsync(fetchMisses.map(this.constructRedisKey)),
|
|
120
|
-
]);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return results;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Invalidate the cache for objects cached by constructRedisKey(loadParams).
|
|
128
|
-
*
|
|
129
|
-
* @param loadParamsArray - load params to invalidate
|
|
130
|
-
*/
|
|
131
|
-
public async invalidateManyAsync(
|
|
132
|
-
loadParamsArray: readonly Readonly<TLoadParams>[]
|
|
133
|
-
): Promise<void> {
|
|
134
|
-
await this.genericRedisCacher.invalidateManyAsync(loadParamsArray.map(this.constructRedisKey));
|
|
16
|
+
super(new GenericRedisCacher(genericRedisCacheContext, entityConfiguration), constructRedisKey);
|
|
135
17
|
}
|
|
136
18
|
}
|