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