@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, ISecondaryEntityCache } from '@expo/entity';
1
+ import { EntityConfiguration, GenericSecondaryEntityCache } from '@expo/entity';
2
2
  import { GenericRedisCacheContext } from '@expo/entity-cache-adapter-redis';
3
3
  /**
4
- * A custom secondary read-through entity cache is a way to add a custom second layer of caching for a particular
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> implements ISecondaryEntityCache<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 custom secondary read-through entity cache is a way to add a custom second layer of caching for a particular
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
- this.entityConfiguration = entityConfiguration;
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":";;;;;AAAA,yCAQsB;AACtB,iFAI0C;AAC1C,0DAAkC;AAElC;;;;GAIG;AACH,MAAqB,yBAAyB;IAK5C,YACmB,mBAAiD,EAClE,wBAAkD,EACjC,iBAA4D;QAF5D,wBAAmB,GAAnB,mBAAmB,CAA8B;QAEjD,sBAAiB,GAAjB,iBAAiB,CAA2C;QAE7E,IAAI,CAAC,kBAAkB,GAAG,IAAI,+CAAkB,CAAC,wBAAwB,CAAC,CAAC;IAC7E,CAAC;IAED;;;;;;;;;;;;OAYG;IACI,KAAK,CAAC,oBAAoB,CAC/B,eAAiD,EACjD,OAE0E;QAE1E,MAAM,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC9D,MAAM,uBAAuB,GAAG,IAAA,iBAAQ,EAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QAErE,MAAM,gBAAgB,GAAG,MAAM,IAAI,CAAC,kBAAkB,CAAC,aAAa,CAAC,SAAS,CAAC,CAAC;QAEhF,IAAA,mBAAS,EACP,gBAAgB,CAAC,IAAI,KAAK,eAAe,CAAC,MAAM,EAChD,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,+CAA+C,CACxE,CAAC;QAEF,MAAM,gBAAgB,GAAG,KAAK,CAAC,IAAI,CACjC,IAAA,kBAAS,EACP,gBAAgB,EAChB,CAAC,eAAe,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,KAAK,oBAAW,CAAC,IAAI,CACjE,CAAC,IAAI,EAAE,CACT,CAAC;QAEF,2CAA2C;QAC3C,MAAM,OAAO,GAAyD,IAAI,GAAG,EAAE,CAAC;QAChF,gBAAgB,CAAC,OAAO,CAAC,CAAC,eAAe,EAAE,QAAQ,EAAE,EAAE;YACrD,IAAI,eAAe,CAAC,MAAM,KAAK,oBAAW,CAAC,GAAG,EAAE;gBAC9C,MAAM,UAAU,GAAG,uBAAuB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACzD,IAAA,mBAAS,EAAC,UAAU,KAAK,SAAS,EAAE,wCAAwC,CAAC,CAAC;gBAC9E,OAAO,CAAC,GAAG,CACT,UAAU,EACV,IAAA,qCAA4B,EAC1B,IAAI,CAAC,mBAAmB,EACxB,gDAAmB,EACnB,eAAe,CAAC,IAAI,CACrB,CACF,CAAC;aACH;QACH,CAAC,CAAC,CAAC;QAEH,gHAAgH;QAChH,IAAI,gBAAgB,CAAC,MAAM,GAAG,CAAC,EAAE;YAC/B,MAAM,iBAAiB,GAAG,gBAAgB,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE;gBAC1D,MAAM,UAAU,GAAG,uBAAuB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBACzD,IAAA,mBAAS,EAAC,UAAU,KAAK,SAAS,EAAE,wCAAwC,CAAC,CAAC;gBAC9E,OAAO,UAAU,CAAC;YACpB,CAAC,CAAC,CAAC;YACH,MAAM,YAAY,GAAG,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC;YAEtD,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC,UAAU,EAAE,EAAE;gBAC1D,kEAAkE;gBAClE,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACvC,CAAC,CAAC,CAAC;YAEH,KAAK,MAAM,SAAS,IAAI,WAAW,EAAE;gBACnC,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;aAC9B;YAED,MAAM,cAAc,GAAwB,IAAI,GAAG,EAAE,CAAC;YACtD,KAAK,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,YAAY,CAAC,OAAO,EAAE,EAAE;gBACzD,IAAI,MAAM,EAAE;oBACV,cAAc,CAAC,GAAG,CAChB,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC,EAClC,IAAA,qCAA4B,EAAC,IAAI,CAAC,mBAAmB,EAAE,gDAAmB,EAAE,MAAM,CAAC,CACpF,CAAC;oBACF,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;iBACjC;aACF;YAED,MAAM,OAAO,CAAC,GAAG,CAAC;gBAChB,IAAI,CAAC,kBAAkB,CAAC,cAAc,CAAC,cAAc,CAAC;gBACtD,IAAI,CAAC,kBAAkB,CAAC,kBAAkB,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;aACpF,CAAC,CAAC;SACJ;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED;;;;OAIG;IACI,KAAK,CAAC,mBAAmB,CAC9B,eAAiD;QAEjD,MAAM,IAAI,CAAC,kBAAkB,CAAC,mBAAmB,CAAC,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACjG,CAAC;CACF;AAlHD,4CAkHC"}
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.22.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.22.0",
38
- "@expo/entity-cache-adapter-redis": "^0.22.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
- CacheStatus,
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 custom secondary read-through entity cache is a way to add a custom second layer of caching for a particular
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<TFields, TLoadParams>
23
- implements ISecondaryEntityCache<TFields, TLoadParams>
24
- {
25
- private readonly genericRedisCacher: GenericRedisCacher;
26
-
7
+ export default class RedisSecondaryEntityCache<
8
+ TFields,
9
+ TLoadParams
10
+ > extends GenericSecondaryEntityCache<TFields, TLoadParams> {
27
11
  constructor(
28
- private readonly entityConfiguration: EntityConfiguration<TFields>,
12
+ entityConfiguration: EntityConfiguration<TFields>,
29
13
  genericRedisCacheContext: GenericRedisCacheContext,
30
- private readonly constructRedisKey: (params: Readonly<TLoadParams>) => string
14
+ constructRedisKey: (params: Readonly<TLoadParams>) => string
31
15
  ) {
32
- this.genericRedisCacher = new GenericRedisCacher(genericRedisCacheContext);
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
  }