mirror of
https://github.com/expo/entity.git
synced 2026-11-13 20:28:06 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b61341057b |
No files matched your search
@@ -143,4 +143,12 @@ export default class GenericLocalMemoryCacher<TFields> implements IEntityGeneric
|
|||||||
this.localMemoryCache.del(key);
|
this.localMemoryCache.del(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public makeCacheKey(parts: string[]): string {
|
||||||
|
const delimiter = ':';
|
||||||
|
const escapedParts = parts.map((part) =>
|
||||||
|
part.replace('\\', '\\\\').replace(delimiter, `\\${delimiter}`)
|
||||||
|
);
|
||||||
|
return escapedParts.join(delimiter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,80 @@
|
|||||||
import { EntityConfiguration, PartsCacheAdapter, Parts } from '@expo/entity';
|
import { EntityCacheAdapter, CacheLoadResult, EntityConfiguration, mapKeys } from '@expo/entity';
|
||||||
import SimplePartsCacher from '@expo/entity/build/SimplePartsCacher';
|
|
||||||
import invariant from 'invariant';
|
import invariant from 'invariant';
|
||||||
|
|
||||||
import GenericLocalMemoryCacher, { LocalMemoryCache } from './GenericLocalMemoryCacher';
|
import GenericLocalMemoryCacher, { LocalMemoryCache } from './GenericLocalMemoryCacher';
|
||||||
|
|
||||||
export default class LocalMemoryCacheAdapter<TFields> extends PartsCacheAdapter<TFields> {
|
export default class LocalMemoryCacheAdapter<TFields> extends EntityCacheAdapter<TFields> {
|
||||||
|
private readonly genericLocalMemoryCacher: GenericLocalMemoryCacher<TFields>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
entityConfiguration: EntityConfiguration<TFields>,
|
entityConfiguration: EntityConfiguration<TFields>,
|
||||||
localMemoryCache: LocalMemoryCache<TFields>
|
localMemoryCache: LocalMemoryCache<TFields>
|
||||||
) {
|
) {
|
||||||
super(
|
super(entityConfiguration);
|
||||||
entityConfiguration,
|
this.genericLocalMemoryCacher = new GenericLocalMemoryCacher(localMemoryCache);
|
||||||
new SimplePartsCacher(new GenericLocalMemoryCacher(localMemoryCache))
|
}
|
||||||
|
|
||||||
|
public async loadManyAsync<N extends keyof TFields>(
|
||||||
|
fieldName: N,
|
||||||
|
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||||
|
): Promise<ReadonlyMap<NonNullable<TFields[N]>, CacheLoadResult<TFields>>> {
|
||||||
|
const localMemoryCacheKeyToFieldValueMapping = new Map(
|
||||||
|
fieldValues.map((fieldValue) => [this.makeCacheKey(fieldName, fieldValue), fieldValue])
|
||||||
|
);
|
||||||
|
const cacheResults = await this.genericLocalMemoryCacher.loadManyAsync(
|
||||||
|
Array.from(localMemoryCacheKeyToFieldValueMapping.keys())
|
||||||
|
);
|
||||||
|
|
||||||
|
return mapKeys(cacheResults, (cacheKey) => {
|
||||||
|
const fieldValue = localMemoryCacheKeyToFieldValueMapping.get(cacheKey);
|
||||||
|
invariant(
|
||||||
|
fieldValue !== undefined,
|
||||||
|
'Unspecified cache key %s returned from generic local memory cacher',
|
||||||
|
cacheKey
|
||||||
|
);
|
||||||
|
return fieldValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async cacheManyAsync<N extends keyof TFields>(
|
||||||
|
fieldName: N,
|
||||||
|
objectMap: ReadonlyMap<NonNullable<TFields[N]>, Readonly<TFields>>
|
||||||
|
): Promise<void> {
|
||||||
|
await this.genericLocalMemoryCacher.cacheManyAsync(
|
||||||
|
mapKeys(objectMap, (fieldValue) => this.makeCacheKey(fieldName, fieldValue))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getParts<N extends keyof TFields>(fieldName: N, fieldValue: NonNullable<TFields[N]>): Parts {
|
public async cacheDBMissesAsync<N extends keyof TFields>(
|
||||||
|
fieldName: N,
|
||||||
|
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||||
|
): Promise<void> {
|
||||||
|
await this.genericLocalMemoryCacher.cacheDBMissesAsync(
|
||||||
|
fieldValues.map((fieldValue) => this.makeCacheKey(fieldName, fieldValue))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async invalidateManyAsync<N extends keyof TFields>(
|
||||||
|
fieldName: N,
|
||||||
|
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||||
|
): Promise<void> {
|
||||||
|
await this.genericLocalMemoryCacher.invalidateManyAsync(
|
||||||
|
fieldValues.map((fieldValue) => this.makeCacheKey(fieldName, fieldValue))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private makeCacheKey<N extends keyof TFields>(
|
||||||
|
fieldName: N,
|
||||||
|
fieldValue: NonNullable<TFields[N]>
|
||||||
|
): string {
|
||||||
const columnName = this.entityConfiguration.entityToDBFieldsKeyMapping.get(fieldName);
|
const columnName = this.entityConfiguration.entityToDBFieldsKeyMapping.get(fieldName);
|
||||||
invariant(columnName, `database field mapping missing for ${fieldName}`);
|
invariant(columnName, `database field mapping missing for ${fieldName}`);
|
||||||
return [
|
const parts = [
|
||||||
this.entityConfiguration.tableName,
|
this.entityConfiguration.tableName,
|
||||||
`${this.entityConfiguration.cacheKeyVersion}`,
|
`${this.entityConfiguration.cacheKeyVersion}`,
|
||||||
columnName,
|
columnName,
|
||||||
String(fieldValue),
|
String(fieldValue),
|
||||||
];
|
];
|
||||||
|
return this.genericLocalMemoryCacher.makeCacheKey(parts);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
import GenericLocalMemoryCacher, { LocalMemoryCache } from './GenericLocalMemoryCacher';
|
import GenericLocalMemoryCacher, { LocalMemoryCache } from './GenericLocalMemoryCacher';
|
||||||
import LocalMemoryCacheAdapter from './LocalMemoryCacheAdapter';
|
import LocalMemoryCacheAdapter from './LocalMemoryCacheAdapter';
|
||||||
|
|
||||||
export class LocalMemoryCacheAdapterProvider implements IEntityCacheAdapterProvider {
|
export default class LocalMemoryCacheAdapterProvider implements IEntityCacheAdapterProvider {
|
||||||
// local memory cache adapters should be shared/reused across requests
|
// local memory cache adapters should be shared/reused across requests
|
||||||
static localMemoryCacheAdapterMap = new Map<string, LocalMemoryCacheAdapter<any>>();
|
static localMemoryCacheAdapterMap = new Map<string, LocalMemoryCacheAdapter<any>>();
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -41,7 +41,7 @@ describe(LocalMemoryCacheAdapter, () => {
|
|||||||
const entitySpecificGenericCacher =
|
const entitySpecificGenericCacher =
|
||||||
LocalMemoryCacheAdapterProvider.localMemoryCacheAdapterMap.get(
|
LocalMemoryCacheAdapterProvider.localMemoryCacheAdapterMap.get(
|
||||||
LocalMemoryTestEntity.getCompanionDefinition().entityConfiguration.tableName
|
LocalMemoryTestEntity.getCompanionDefinition().entityConfiguration.tableName
|
||||||
)!['partsCacher']['cacher'];
|
)!['genericLocalMemoryCacher'];
|
||||||
const cachedResult = await entitySpecificGenericCacher.loadManyAsync([
|
const cachedResult = await entitySpecificGenericCacher.loadManyAsync([
|
||||||
cacheKeyMaker('id', entity1.getID()),
|
cacheKeyMaker('id', entity1.getID()),
|
||||||
]);
|
]);
|
||||||
@@ -153,7 +153,7 @@ describe(LocalMemoryCacheAdapter, () => {
|
|||||||
const entitySpecificGenericCacher =
|
const entitySpecificGenericCacher =
|
||||||
LocalMemoryCacheAdapterProvider.localMemoryCacheAdapterMap.get(
|
LocalMemoryCacheAdapterProvider.localMemoryCacheAdapterMap.get(
|
||||||
LocalMemoryTestEntity.getCompanionDefinition().entityConfiguration.tableName
|
LocalMemoryTestEntity.getCompanionDefinition().entityConfiguration.tableName
|
||||||
)!['partsCacher']['cacher'];
|
)!['genericLocalMemoryCacher'];
|
||||||
const cachedResult = await entitySpecificGenericCacher.loadManyAsync([
|
const cachedResult = await entitySpecificGenericCacher.loadManyAsync([
|
||||||
cacheKeyMaker('id', entity1.getID()),
|
cacheKeyMaker('id', entity1.getID()),
|
||||||
]);
|
]);
|
||||||
|
|||||||
+2
-2
@@ -72,7 +72,7 @@ describe(LocalMemoryCacheAdapter, () => {
|
|||||||
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
|
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
|
||||||
await cacheAdapter.cacheManyAsync('id', new Map([['test-id-1', { id: 'test-id-1' }]]));
|
await cacheAdapter.cacheManyAsync('id', new Map([['test-id-1', { id: 'test-id-1' }]]));
|
||||||
|
|
||||||
const cacheKey = cacheAdapter['partsCacher']['makeCacheKey']('id', 'test-id-1');
|
const cacheKey = cacheAdapter['makeCacheKey']('id', 'test-id-1');
|
||||||
expect(localMemoryCache.get(cacheKey)).toMatchObject({
|
expect(localMemoryCache.get(cacheKey)).toMatchObject({
|
||||||
id: 'test-id-1',
|
id: 'test-id-1',
|
||||||
});
|
});
|
||||||
@@ -89,7 +89,7 @@ describe(LocalMemoryCacheAdapter, () => {
|
|||||||
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
|
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
|
||||||
await cacheAdapter.cacheDBMissesAsync('id', ['test-id-1']);
|
await cacheAdapter.cacheDBMissesAsync('id', ['test-id-1']);
|
||||||
|
|
||||||
const cacheKey = cacheAdapter['partsCacher']['makeCacheKey']('id', 'test-id-1');
|
const cacheKey = cacheAdapter['makeCacheKey']('id', 'test-id-1');
|
||||||
expect(localMemoryCache.get(cacheKey)).toEqual(DOES_NOT_EXIST_LOCAL_MEMORY_CACHE);
|
expect(localMemoryCache.get(cacheKey)).toEqual(DOES_NOT_EXIST_LOCAL_MEMORY_CACHE);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/* eslint-disable tsdoc/syntax */
|
||||||
|
/**
|
||||||
|
* @packageDocumentation
|
||||||
|
* @module @expo/entity-cache-adapter-local-memory
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { default as LocalMemoryCacheAdapter } from './LocalMemoryCacheAdapter';
|
||||||
|
export { default as LocalMemoryCacheAdapterProvider } from './LocalMemoryCacheAdapterProvider';
|
||||||
|
export { default as GenericLocalMemoryCacher } from './GenericLocalMemoryCacher';
|
||||||
|
export * from './GenericLocalMemoryCacher';
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { EntityConfiguration, PartsCacheAdapter, Parts } from '@expo/entity';
|
import { EntityCacheAdapter, EntityConfiguration, CacheLoadResult, mapKeys } from '@expo/entity';
|
||||||
import SimplePartsCacher from '@expo/entity/build/SimplePartsCacher';
|
|
||||||
import invariant from 'invariant';
|
import invariant from 'invariant';
|
||||||
import { Redis } from 'ioredis';
|
import { Redis } from 'ioredis';
|
||||||
|
|
||||||
@@ -42,36 +41,85 @@ export interface RedisCacheAdapterContext {
|
|||||||
ttlSecondsNegative: number;
|
ttlSecondsNegative: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class RedisCacheAdapter<TFields> extends PartsCacheAdapter<TFields> {
|
export default class RedisCacheAdapter<TFields> extends EntityCacheAdapter<TFields> {
|
||||||
|
private readonly genericRedisCacher: GenericRedisCacher<TFields>;
|
||||||
constructor(
|
constructor(
|
||||||
entityConfiguration: EntityConfiguration<TFields>,
|
entityConfiguration: EntityConfiguration<TFields>,
|
||||||
private readonly context: RedisCacheAdapterContext
|
private readonly context: RedisCacheAdapterContext
|
||||||
) {
|
) {
|
||||||
super(
|
super(entityConfiguration);
|
||||||
entityConfiguration,
|
|
||||||
new SimplePartsCacher(
|
this.genericRedisCacher = new GenericRedisCacher(
|
||||||
new GenericRedisCacher(
|
|
||||||
{
|
{
|
||||||
redisClient: context.redisClient,
|
redisClient: context.redisClient,
|
||||||
ttlSecondsNegative: context.ttlSecondsNegative,
|
ttlSecondsNegative: context.ttlSecondsNegative,
|
||||||
ttlSecondsPositive: context.ttlSecondsPositive,
|
ttlSecondsPositive: context.ttlSecondsPositive,
|
||||||
},
|
},
|
||||||
entityConfiguration
|
entityConfiguration
|
||||||
),
|
|
||||||
context.makeKeyFn
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getParts<N extends keyof TFields>(fieldName: N, fieldValue: NonNullable<TFields[N]>): Parts {
|
public async loadManyAsync<N extends keyof TFields>(
|
||||||
|
fieldName: N,
|
||||||
|
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||||
|
): Promise<ReadonlyMap<NonNullable<TFields[N]>, CacheLoadResult<TFields>>> {
|
||||||
|
const redisCacheKeyToFieldValueMapping = new Map(
|
||||||
|
fieldValues.map((fieldValue) => [this.makeCacheKey(fieldName, fieldValue), fieldValue])
|
||||||
|
);
|
||||||
|
const cacheResults = await this.genericRedisCacher.loadManyAsync(
|
||||||
|
Array.from(redisCacheKeyToFieldValueMapping.keys())
|
||||||
|
);
|
||||||
|
|
||||||
|
return mapKeys(cacheResults, (redisCacheKey) => {
|
||||||
|
const fieldValue = redisCacheKeyToFieldValueMapping.get(redisCacheKey);
|
||||||
|
invariant(
|
||||||
|
fieldValue !== undefined,
|
||||||
|
'Unspecified cache key %s returned from generic Redis cacher',
|
||||||
|
redisCacheKey
|
||||||
|
);
|
||||||
|
return fieldValue;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async cacheManyAsync<N extends keyof TFields>(
|
||||||
|
fieldName: N,
|
||||||
|
objectMap: ReadonlyMap<NonNullable<TFields[N]>, Readonly<TFields>>
|
||||||
|
): Promise<void> {
|
||||||
|
await this.genericRedisCacher.cacheManyAsync(
|
||||||
|
mapKeys(objectMap, (fieldValue) => this.makeCacheKey(fieldName, fieldValue))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async cacheDBMissesAsync<N extends keyof TFields>(
|
||||||
|
fieldName: N,
|
||||||
|
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||||
|
): Promise<void> {
|
||||||
|
await this.genericRedisCacher.cacheDBMissesAsync(
|
||||||
|
fieldValues.map((fieldValue) => this.makeCacheKey(fieldName, fieldValue))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async invalidateManyAsync<N extends keyof TFields>(
|
||||||
|
fieldName: N,
|
||||||
|
fieldValues: readonly NonNullable<TFields[N]>[]
|
||||||
|
): Promise<void> {
|
||||||
|
await this.genericRedisCacher.invalidateManyAsync(
|
||||||
|
fieldValues.map((fieldValue) => this.makeCacheKey(fieldName, fieldValue))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private makeCacheKey<N extends keyof TFields>(
|
||||||
|
fieldName: N,
|
||||||
|
fieldValue: NonNullable<TFields[N]>
|
||||||
|
): string {
|
||||||
const columnName = this.entityConfiguration.entityToDBFieldsKeyMapping.get(fieldName);
|
const columnName = this.entityConfiguration.entityToDBFieldsKeyMapping.get(fieldName);
|
||||||
invariant(columnName, `database field mapping missing for ${fieldName}`);
|
invariant(columnName, `database field mapping missing for ${fieldName}`);
|
||||||
return [
|
return this.context.makeKeyFn(
|
||||||
this.context.cacheKeyPrefix,
|
this.context.cacheKeyPrefix,
|
||||||
this.entityConfiguration.tableName,
|
this.entityConfiguration.tableName,
|
||||||
`v2.${this.entityConfiguration.cacheKeyVersion}`,
|
`v2.${this.entityConfiguration.cacheKeyVersion}`,
|
||||||
columnName,
|
columnName,
|
||||||
String(fieldValue),
|
String(fieldValue)
|
||||||
];
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -39,11 +39,8 @@ describe(RedisCacheAdapter, () => {
|
|||||||
ttlSecondsNegative: 2,
|
ttlSecondsNegative: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
redisResults.set(
|
redisResults.set(cacheAdapter['makeCacheKey']('id', 'wat'), JSON.stringify({ id: 'wat' }));
|
||||||
cacheAdapter['partsCacher']['makeCacheKey']('id', 'wat'),
|
redisResults.set(cacheAdapter['makeCacheKey']('id', 'who'), '');
|
||||||
JSON.stringify({ id: 'wat' })
|
|
||||||
);
|
|
||||||
redisResults.set(cacheAdapter['partsCacher']['makeCacheKey']('id', 'who'), '');
|
|
||||||
|
|
||||||
const results = await cacheAdapter.loadManyAsync('id', ['wat', 'who', 'why']);
|
const results = await cacheAdapter.loadManyAsync('id', ['wat', 'who', 'why']);
|
||||||
|
|
||||||
@@ -94,7 +91,7 @@ describe(RedisCacheAdapter, () => {
|
|||||||
});
|
});
|
||||||
await cacheAdapter.cacheManyAsync('id', new Map([['wat', { id: 'wat' }]]));
|
await cacheAdapter.cacheManyAsync('id', new Map([['wat', { id: 'wat' }]]));
|
||||||
|
|
||||||
const cacheKey = cacheAdapter['partsCacher']['makeCacheKey']('id', 'wat');
|
const cacheKey = cacheAdapter['makeCacheKey']('id', 'wat');
|
||||||
expect(redisResults.get(cacheKey)).toMatchObject({
|
expect(redisResults.get(cacheKey)).toMatchObject({
|
||||||
value: JSON.stringify({ id: 'wat' }),
|
value: JSON.stringify({ id: 'wat' }),
|
||||||
code: 'EX',
|
code: 'EX',
|
||||||
@@ -129,7 +126,7 @@ describe(RedisCacheAdapter, () => {
|
|||||||
});
|
});
|
||||||
await cacheAdapter.cacheDBMissesAsync('id', ['wat']);
|
await cacheAdapter.cacheDBMissesAsync('id', ['wat']);
|
||||||
|
|
||||||
const cacheKey = cacheAdapter['partsCacher']['makeCacheKey']('id', 'wat');
|
const cacheKey = cacheAdapter['makeCacheKey']('id', 'wat');
|
||||||
expect(redisResults.get(cacheKey)).toMatchObject({
|
expect(redisResults.get(cacheKey)).toMatchObject({
|
||||||
value: '',
|
value: '',
|
||||||
code: 'EX',
|
code: 'EX',
|
||||||
@@ -153,7 +150,7 @@ describe(RedisCacheAdapter, () => {
|
|||||||
});
|
});
|
||||||
await cacheAdapter.invalidateManyAsync('id', ['wat']);
|
await cacheAdapter.invalidateManyAsync('id', ['wat']);
|
||||||
|
|
||||||
const cacheKey = cacheAdapter['partsCacher']['makeCacheKey']('id', 'wat');
|
const cacheKey = cacheAdapter['makeCacheKey']('id', 'wat');
|
||||||
verify(mockRedisClient.del(cacheKey)).once();
|
verify(mockRedisClient.del(cacheKey)).once();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,4 @@
|
|||||||
import {
|
import { EntityConfiguration, GenericSecondaryEntityCache } from '@expo/entity';
|
||||||
EntityConfiguration,
|
|
||||||
GenericSecondaryEntityCache,
|
|
||||||
Parts,
|
|
||||||
SimplePartsCacher,
|
|
||||||
} from '@expo/entity';
|
|
||||||
import { GenericRedisCacheContext, GenericRedisCacher } from '@expo/entity-cache-adapter-redis';
|
import { GenericRedisCacheContext, GenericRedisCacher } from '@expo/entity-cache-adapter-redis';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,15 +13,8 @@ export default class RedisSecondaryEntityCache<
|
|||||||
constructor(
|
constructor(
|
||||||
entityConfiguration: EntityConfiguration<TFields>,
|
entityConfiguration: EntityConfiguration<TFields>,
|
||||||
genericRedisCacheContext: GenericRedisCacheContext,
|
genericRedisCacheContext: GenericRedisCacheContext,
|
||||||
makeRedisSpecificKey: (...parts: Parts) => string,
|
constructRedisKey: (params: Readonly<TLoadParams>) => string
|
||||||
getParts: (params: Readonly<TLoadParams>) => Parts
|
|
||||||
) {
|
) {
|
||||||
super(
|
super(new GenericRedisCacher(genericRedisCacheContext, entityConfiguration), constructRedisKey);
|
||||||
new SimplePartsCacher(
|
|
||||||
new GenericRedisCacher(genericRedisCacheContext, entityConfiguration),
|
|
||||||
makeRedisSpecificKey
|
|
||||||
),
|
|
||||||
getParts
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+2
-4
@@ -87,8 +87,7 @@ describe(RedisSecondaryEntityCache, () => {
|
|||||||
new RedisSecondaryEntityCache(
|
new RedisSecondaryEntityCache(
|
||||||
redisTestEntityConfiguration,
|
redisTestEntityConfiguration,
|
||||||
redisCacheAdapterContext,
|
redisCacheAdapterContext,
|
||||||
(...parts) => parts.join(':redis-specific-delimiter:'),
|
(loadParams) => `test-key-${loadParams.id}`
|
||||||
(loadParams) => [`test-key-${loadParams.id}`]
|
|
||||||
),
|
),
|
||||||
RedisTestEntity.loader(viewerContext)
|
RedisTestEntity.loader(viewerContext)
|
||||||
);
|
);
|
||||||
@@ -127,8 +126,7 @@ describe(RedisSecondaryEntityCache, () => {
|
|||||||
new RedisSecondaryEntityCache(
|
new RedisSecondaryEntityCache(
|
||||||
redisTestEntityConfiguration,
|
redisTestEntityConfiguration,
|
||||||
redisCacheAdapterContext,
|
redisCacheAdapterContext,
|
||||||
(...parts) => parts.join(':redis-specific-delimiter:'),
|
(loadParams) => `test-key-${loadParams.id}`
|
||||||
(loadParams) => [`test-key-${loadParams.id}`]
|
|
||||||
),
|
),
|
||||||
RedisTestEntity.loader(viewerContext)
|
RedisTestEntity.loader(viewerContext)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
import { CacheLoadResult } from '@expo/entity/src/internal/ReadThroughEntityCache';
|
|
||||||
import invariant from 'invariant';
|
|
||||||
|
|
||||||
import PartsCacher, { Parts, PartsKey } from './PartsCacher';
|
|
||||||
import { CacheStatus } from './internal/ReadThroughEntityCache';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A composed cacher by which objects are stored, fetched and removed in a series of caches
|
|
||||||
* */
|
|
||||||
export default class ComposedPartsCacher<TFields> extends PartsCacher<TFields> {
|
|
||||||
constructor(
|
|
||||||
/**
|
|
||||||
* The series of cachers to use, in order of evaluation
|
|
||||||
* For example, [cacheAdapterEvaluatedFirst, cacheAdapterEvaluatedSecond, cacheAdapterEvaluatedThird]
|
|
||||||
*/
|
|
||||||
private readonly partsCachers: PartsCacher<TFields>[]
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async loadManyAsync(
|
|
||||||
partsList: readonly Parts[]
|
|
||||||
): Promise<ReadonlyMap<PartsKey, CacheLoadResult<TFields>>> {
|
|
||||||
const resultsFromAllCachers = new Map<PartsKey, CacheLoadResult<TFields>>();
|
|
||||||
const partsKeysToParts = new Map(
|
|
||||||
partsList.map((parts) => [PartsCacher.getPartsKey(...parts), parts])
|
|
||||||
);
|
|
||||||
let partsKeysToLookup: PartsKey[] = partsList.map((parts) => PartsCacher.getPartsKey(...parts));
|
|
||||||
for (const partsCacher of this.partsCachers) {
|
|
||||||
const partsListToLookup = partsKeysToLookup.map((partsKey) => {
|
|
||||||
const parts = partsKeysToParts.get(partsKey);
|
|
||||||
invariant(parts, 'parts cannot be undefined');
|
|
||||||
return parts;
|
|
||||||
});
|
|
||||||
const cacheResults = await partsCacher.loadManyAsync(partsListToLookup);
|
|
||||||
const cacheMisses = [];
|
|
||||||
for (const [partsKey, cacheResult] of cacheResults) {
|
|
||||||
if (cacheResult.status === CacheStatus.MISS) {
|
|
||||||
cacheMisses.push(partsKey);
|
|
||||||
} else {
|
|
||||||
resultsFromAllCachers.set(partsKey, cacheResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
partsKeysToLookup = cacheMisses;
|
|
||||||
}
|
|
||||||
|
|
||||||
// None of the cachers have these values, so they are misses
|
|
||||||
for (const partsKey of partsKeysToLookup) {
|
|
||||||
resultsFromAllCachers.set(partsKey, {
|
|
||||||
status: CacheStatus.MISS,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return resultsFromAllCachers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async cacheManyAsync(objectMap: ReadonlyMap<PartsKey, Readonly<TFields>>): Promise<void> {
|
|
||||||
// perform action on lower layers first
|
|
||||||
for (let i = this.partsCachers.length - 1; i >= 0; i--) {
|
|
||||||
const partsCacher = this.partsCachers[i];
|
|
||||||
invariant(partsCacher, 'parts cacher cannot be undefined');
|
|
||||||
await partsCacher.cacheManyAsync(objectMap);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async cacheDBMissesAsync(partsList: readonly Parts[]): Promise<void> {
|
|
||||||
// perform action on lower layers first
|
|
||||||
for (let i = this.partsCachers.length - 1; i >= 0; i--) {
|
|
||||||
const partsCacher = this.partsCachers[i];
|
|
||||||
invariant(partsCacher, 'parts cacher cannot be undefined');
|
|
||||||
await partsCacher.cacheDBMissesAsync(partsList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async invalidateManyAsync(partsList: readonly Parts[]): Promise<void> {
|
|
||||||
// perform action on lower layers first
|
|
||||||
for (let i = this.partsCachers.length - 1; i >= 0; i--) {
|
|
||||||
const cacheAdapter = this.partsCachers[i];
|
|
||||||
invariant(cacheAdapter, 'parts cacher cannot be undefined');
|
|
||||||
await cacheAdapter.invalidateManyAsync(partsList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import invariant from 'invariant';
|
import invariant from 'invariant';
|
||||||
|
|
||||||
import { ISecondaryEntityCache } from './EntitySecondaryCacheLoader';
|
import { ISecondaryEntityCache } from './EntitySecondaryCacheLoader';
|
||||||
import PartsCacher, { Parts, PartsKey } from './PartsCacher';
|
import IEntityGenericCacher from './IEntityGenericCacher';
|
||||||
import { CacheStatus } from './internal/ReadThroughEntityCache';
|
import { CacheStatus } from './internal/ReadThroughEntityCache';
|
||||||
import { filterMap } from './utils/collections/maps';
|
import { filterMap, zipToMap } from './utils/collections/maps';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A custom secondary read-through entity cache is a way to add a custom second layer of caching for a particular
|
* A custom secondary read-through entity cache is a way to add a custom second layer of caching for a particular
|
||||||
@@ -14,8 +14,8 @@ export default abstract class GenericSecondaryEntityCache<TFields, TLoadParams>
|
|||||||
implements ISecondaryEntityCache<TFields, TLoadParams>
|
implements ISecondaryEntityCache<TFields, TLoadParams>
|
||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
protected readonly partsCacher: PartsCacher<TFields>,
|
protected readonly cacher: IEntityGenericCacher<TFields>,
|
||||||
protected readonly getParts: (params: Readonly<TLoadParams>) => Parts
|
protected readonly constructCacheKey: (params: Readonly<TLoadParams>) => string
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public async loadManyThroughAsync(
|
public async loadManyThroughAsync(
|
||||||
@@ -24,22 +24,17 @@ export default abstract class GenericSecondaryEntityCache<TFields, TLoadParams>
|
|||||||
fetcherLoadParamsArray: readonly Readonly<TLoadParams>[]
|
fetcherLoadParamsArray: readonly Readonly<TLoadParams>[]
|
||||||
) => Promise<ReadonlyMap<Readonly<TLoadParams>, Readonly<TFields> | null>>
|
) => Promise<ReadonlyMap<Readonly<TLoadParams>, Readonly<TFields> | null>>
|
||||||
): Promise<ReadonlyMap<Readonly<TLoadParams>, Readonly<TFields> | null>> {
|
): Promise<ReadonlyMap<Readonly<TLoadParams>, Readonly<TFields> | null>> {
|
||||||
const partsList = loadParamsArray.map(this.getParts);
|
const cacheKeys = loadParamsArray.map(this.constructCacheKey);
|
||||||
const partsKeysToLoadParamsMap = new Map(
|
const cacheKeyToLoadParamsMap = zipToMap(cacheKeys, loadParamsArray);
|
||||||
loadParamsArray.map((loadParams) => {
|
|
||||||
const parts = this.getParts(loadParams);
|
|
||||||
return [PartsCacher.getPartsKey(...parts), loadParams];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const cacheLoadResults = await this.partsCacher.loadManyAsync(partsList);
|
const cacheLoadResults = await this.cacher.loadManyAsync(cacheKeys);
|
||||||
|
|
||||||
invariant(
|
invariant(
|
||||||
cacheLoadResults.size === loadParamsArray.length,
|
cacheLoadResults.size === loadParamsArray.length,
|
||||||
`${this.constructor.name} loadMany should return a result for each key`
|
`${this.constructor.name} loadMany should return a result for each key`
|
||||||
);
|
);
|
||||||
|
|
||||||
const partsKeysToFetch = Array.from(
|
const cacheKeysToFetch = Array.from(
|
||||||
filterMap(
|
filterMap(
|
||||||
cacheLoadResults,
|
cacheLoadResults,
|
||||||
(cacheLoadResult) => cacheLoadResult.status === CacheStatus.MISS
|
(cacheLoadResult) => cacheLoadResult.status === CacheStatus.MISS
|
||||||
@@ -48,18 +43,18 @@ export default abstract class GenericSecondaryEntityCache<TFields, TLoadParams>
|
|||||||
|
|
||||||
// put cache hits in result map
|
// put cache hits in result map
|
||||||
const results: Map<Readonly<TLoadParams>, Readonly<TFields> | null> = new Map();
|
const results: Map<Readonly<TLoadParams>, Readonly<TFields> | null> = new Map();
|
||||||
cacheLoadResults.forEach((cacheLoadResult, partsKey) => {
|
cacheLoadResults.forEach((cacheLoadResult, cacheKey) => {
|
||||||
if (cacheLoadResult.status === CacheStatus.HIT) {
|
if (cacheLoadResult.status === CacheStatus.HIT) {
|
||||||
const loadParams = partsKeysToLoadParamsMap.get(partsKey);
|
const loadParams = cacheKeyToLoadParamsMap.get(cacheKey);
|
||||||
invariant(loadParams !== undefined, 'load params should be in cache key map');
|
invariant(loadParams !== undefined, 'load params should be in cache key map');
|
||||||
results.set(loadParams, cacheLoadResult.item);
|
results.set(loadParams, cacheLoadResult.item);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// fetch any misses from DB, add DB objects to results, cache DB results, inform cache of any missing DB results
|
// fetch any misses from DB, add DB objects to results, cache DB results, inform cache of any missing DB results
|
||||||
if (partsKeysToFetch.length > 0) {
|
if (cacheKeysToFetch.length > 0) {
|
||||||
const loadParamsToFetch = partsKeysToFetch.map((partKey) => {
|
const loadParamsToFetch = cacheKeysToFetch.map((cacheKey) => {
|
||||||
const loadParams = partsKeysToLoadParamsMap.get(partKey);
|
const loadParams = cacheKeyToLoadParamsMap.get(cacheKey);
|
||||||
invariant(loadParams !== undefined, 'load params should be in cache key map');
|
invariant(loadParams !== undefined, 'load params should be in cache key map');
|
||||||
return loadParams;
|
return loadParams;
|
||||||
});
|
});
|
||||||
@@ -74,20 +69,17 @@ export default abstract class GenericSecondaryEntityCache<TFields, TLoadParams>
|
|||||||
results.set(fetchMiss, null);
|
results.set(fetchMiss, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectsToCache: Map<PartsKey, Readonly<TFields>> = new Map();
|
const objectsToCache: Map<string, Readonly<TFields>> = new Map();
|
||||||
for (const [loadParams, object] of fetchResults.entries()) {
|
for (const [loadParams, object] of fetchResults.entries()) {
|
||||||
if (object) {
|
if (object) {
|
||||||
const parts = this.getParts(loadParams);
|
objectsToCache.set(this.constructCacheKey(loadParams), object);
|
||||||
objectsToCache.set(PartsCacher.getPartsKey(...parts), object);
|
|
||||||
results.set(loadParams, object);
|
results.set(loadParams, object);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.partsCacher.cacheManyAsync(objectsToCache),
|
this.cacher.cacheManyAsync(objectsToCache),
|
||||||
this.partsCacher.cacheDBMissesAsync(
|
this.cacher.cacheDBMissesAsync(fetchMisses.map(this.constructCacheKey)),
|
||||||
fetchMisses.map((loadParams) => this.getParts(loadParams))
|
|
||||||
),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,7 +92,7 @@ export default abstract class GenericSecondaryEntityCache<TFields, TLoadParams>
|
|||||||
* @param loadParamsArray - load params to invalidate
|
* @param loadParamsArray - load params to invalidate
|
||||||
*/
|
*/
|
||||||
public invalidateManyAsync(loadParamsArray: readonly Readonly<TLoadParams>[]): Promise<void> {
|
public invalidateManyAsync(loadParamsArray: readonly Readonly<TLoadParams>[]): Promise<void> {
|
||||||
const partsList = loadParamsArray.map(this.getParts);
|
const cacheKeys = loadParamsArray.map(this.constructCacheKey);
|
||||||
return this.partsCacher.invalidateManyAsync(partsList);
|
return this.cacher.invalidateManyAsync(cacheKeys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
import { RedisCacheAdapterContext } from '@expo/entity-cache-adapter-redis';
|
|
||||||
import invariant from 'invariant';
|
|
||||||
|
|
||||||
import ComposedPartsCacher from './ComposedPartsCacher';
|
|
||||||
import EntityConfiguration from './EntityConfiguration';
|
|
||||||
import PartsCacheAdapter from './PartsCacheAdapter';
|
|
||||||
import PartsCacher, { Parts } from './PartsCacher';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: put this in www
|
|
||||||
*/
|
|
||||||
export default class LocalMemoryAndRedisPartsCacheAdapter<
|
|
||||||
TFields
|
|
||||||
> extends PartsCacheAdapter<TFields> {
|
|
||||||
constructor(
|
|
||||||
entityConfiguration: EntityConfiguration<TFields>,
|
|
||||||
localMemoryPartsCacher: PartsCacher<TFields>,
|
|
||||||
redisPartsCacher: PartsCacher<TFields>,
|
|
||||||
private readonly redisCacheAdapterContext: RedisCacheAdapterContext
|
|
||||||
) {
|
|
||||||
super(
|
|
||||||
entityConfiguration,
|
|
||||||
new ComposedPartsCacher<TFields>([localMemoryPartsCacher, redisPartsCacher])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: this seems wrong - look at moving the redis specific bits into the makeCacheSpecificKey of the SimplePartsCacher
|
|
||||||
*/
|
|
||||||
getParts<N extends keyof TFields>(fieldName: N, fieldValue: NonNullable<TFields[N]>): Parts {
|
|
||||||
const columnName = this.entityConfiguration.entityToDBFieldsKeyMapping.get(fieldName);
|
|
||||||
invariant(columnName, `database field mapping missing for ${fieldName}`);
|
|
||||||
return [
|
|
||||||
this.redisCacheAdapterContext.cacheKeyPrefix,
|
|
||||||
this.entityConfiguration.tableName,
|
|
||||||
`v2.${this.entityConfiguration.cacheKeyVersion}`,
|
|
||||||
columnName,
|
|
||||||
String(fieldValue),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import ComposedPartsCacher from './ComposedPartsCacher';
|
|
||||||
import GenericSecondaryEntityCache from './GenericSecondaryEntityCache';
|
|
||||||
import PartsCacher, { Parts } from './PartsCacher';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: put this in www
|
|
||||||
*/
|
|
||||||
export default class LocalMemoryAndRedisSecondaryEntityCache<
|
|
||||||
TFields,
|
|
||||||
TLoadParams
|
|
||||||
> extends GenericSecondaryEntityCache<TFields, TLoadParams> {
|
|
||||||
constructor(
|
|
||||||
localMemoryPartsCacher: PartsCacher<TFields>,
|
|
||||||
redisPartsCacher: PartsCacher<TFields>,
|
|
||||||
getParts: (params: Readonly<TLoadParams>) => Parts
|
|
||||||
) {
|
|
||||||
super(new ComposedPartsCacher([localMemoryPartsCacher, redisPartsCacher]), getParts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import invariant from 'invariant';
|
|
||||||
|
|
||||||
import EntityCacheAdapter from './EntityCacheAdapter';
|
|
||||||
import EntityConfiguration from './EntityConfiguration';
|
|
||||||
import PartsCacher, { Parts } from './PartsCacher';
|
|
||||||
import { CacheLoadResult } from './internal/ReadThroughEntityCache';
|
|
||||||
import { mapKeys } from './utils/collections/maps';
|
|
||||||
|
|
||||||
export default abstract class PartsCacheAdapter<TFields> extends EntityCacheAdapter<TFields> {
|
|
||||||
constructor(
|
|
||||||
entityConfiguration: EntityConfiguration<TFields>,
|
|
||||||
private readonly partsCacher: PartsCacher<TFields>
|
|
||||||
) {
|
|
||||||
super(entityConfiguration);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async loadManyAsync<N extends keyof TFields>(
|
|
||||||
fieldName: N,
|
|
||||||
fieldValues: readonly NonNullable<TFields[N]>[]
|
|
||||||
): Promise<ReadonlyMap<NonNullable<TFields[N]>, CacheLoadResult<TFields>>> {
|
|
||||||
const partsList = fieldValues.map((fieldValue) => this.getParts(fieldName, fieldValue));
|
|
||||||
const partsKeysToFieldValueMapping = new Map(
|
|
||||||
fieldValues.map((fieldValue) => {
|
|
||||||
const parts = this.getParts(fieldName, fieldValue);
|
|
||||||
return [PartsCacher.getPartsKey(...parts), fieldValue];
|
|
||||||
})
|
|
||||||
);
|
|
||||||
const cacheResults = await this.partsCacher.loadManyAsync(partsList);
|
|
||||||
|
|
||||||
return mapKeys(cacheResults, (partsKey) => {
|
|
||||||
const fieldValue = partsKeysToFieldValueMapping.get(partsKey);
|
|
||||||
invariant(
|
|
||||||
fieldValue !== undefined,
|
|
||||||
'Unspecified cache key %s returned from Redis parts cacher',
|
|
||||||
partsKey
|
|
||||||
);
|
|
||||||
return fieldValue;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async cacheManyAsync<N extends keyof TFields>(
|
|
||||||
fieldName: N,
|
|
||||||
objectMap: ReadonlyMap<NonNullable<TFields[N]>, Readonly<TFields>>
|
|
||||||
): Promise<void> {
|
|
||||||
const partsKeyToObject = mapKeys(objectMap, (fieldValue) => {
|
|
||||||
const parts = this.getParts(fieldName, fieldValue);
|
|
||||||
return PartsCacher.getPartsKey(...parts);
|
|
||||||
});
|
|
||||||
await this.partsCacher.cacheManyAsync(partsKeyToObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async cacheDBMissesAsync<N extends keyof TFields>(
|
|
||||||
fieldName: N,
|
|
||||||
fieldValues: readonly NonNullable<TFields[N]>[]
|
|
||||||
): Promise<void> {
|
|
||||||
await this.partsCacher.cacheDBMissesAsync(
|
|
||||||
fieldValues.map((fieldValue) => this.getParts(fieldName, fieldValue))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async invalidateManyAsync<N extends keyof TFields>(
|
|
||||||
fieldName: N,
|
|
||||||
fieldValues: readonly NonNullable<TFields[N]>[]
|
|
||||||
): Promise<void> {
|
|
||||||
await this.partsCacher.invalidateManyAsync(
|
|
||||||
fieldValues.map((fieldValue) => this.getParts(fieldName, fieldValue))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract getParts<N extends keyof TFields>(
|
|
||||||
fieldName: N,
|
|
||||||
fieldValue: NonNullable<TFields[N]>
|
|
||||||
): Parts;
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import { CacheLoadResult } from '@expo/entity/src/internal/ReadThroughEntityCache';
|
|
||||||
|
|
||||||
export type Parts = string[];
|
|
||||||
export type PartsKey = string;
|
|
||||||
export default abstract class PartsCacher<TFields> {
|
|
||||||
public abstract loadManyAsync(
|
|
||||||
partsList: readonly Parts[]
|
|
||||||
): Promise<ReadonlyMap<PartsKey, CacheLoadResult<TFields>>>;
|
|
||||||
|
|
||||||
public abstract cacheManyAsync(
|
|
||||||
objectMap: ReadonlyMap<PartsKey, Readonly<TFields>>
|
|
||||||
): Promise<void>;
|
|
||||||
|
|
||||||
public abstract cacheDBMissesAsync(partsList: readonly Parts[]): Promise<void>;
|
|
||||||
|
|
||||||
public abstract invalidateManyAsync(partsList: readonly Parts[]): Promise<void>;
|
|
||||||
|
|
||||||
public static getPartsKey(...parts: Parts): PartsKey {
|
|
||||||
return JSON.stringify(parts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static getPartsFromKey(partKey: PartsKey): Parts {
|
|
||||||
return JSON.parse(partKey);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import IEntityGenericCacher from '@expo/entity/src/IEntityGenericCacher';
|
|
||||||
import { CacheLoadResult } from '@expo/entity/src/internal/ReadThroughEntityCache';
|
|
||||||
import { mapKeys } from '@expo/entity/src/utils/collections/maps';
|
|
||||||
import invariant from 'invariant';
|
|
||||||
|
|
||||||
import PartsCacher, { Parts, PartsKey } from './PartsCacher';
|
|
||||||
|
|
||||||
export default class SimplePartsCacher<TFields> extends PartsCacher<TFields> {
|
|
||||||
constructor(
|
|
||||||
private readonly cacher: IEntityGenericCacher<TFields>,
|
|
||||||
private readonly makeCacheSpecificKey: (...parts: Parts) => string = PartsCacher.getPartsKey
|
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async loadManyAsync(
|
|
||||||
partsList: readonly Parts[]
|
|
||||||
): Promise<ReadonlyMap<PartsKey, CacheLoadResult<TFields>>> {
|
|
||||||
const cacheSpecificKeyToPartKeyMapping = new Map(
|
|
||||||
partsList.map((parts) => [
|
|
||||||
this.makeCacheSpecificKey(...parts),
|
|
||||||
PartsCacher.getPartsKey(...parts),
|
|
||||||
])
|
|
||||||
);
|
|
||||||
const cacheResults = await this.cacher.loadManyAsync(
|
|
||||||
Array.from(cacheSpecificKeyToPartKeyMapping.keys())
|
|
||||||
);
|
|
||||||
|
|
||||||
return mapKeys(cacheResults, (cacheKey) => {
|
|
||||||
const partKey = cacheSpecificKeyToPartKeyMapping.get(cacheKey);
|
|
||||||
invariant(
|
|
||||||
partKey !== undefined,
|
|
||||||
'Unspecified cache key %s returned from generic local memory cacher',
|
|
||||||
cacheKey
|
|
||||||
);
|
|
||||||
return partKey;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public async cacheManyAsync(objectMap: ReadonlyMap<PartsKey, Readonly<TFields>>): Promise<void> {
|
|
||||||
await this.cacher.cacheManyAsync(
|
|
||||||
mapKeys(objectMap, (partKey) =>
|
|
||||||
this.makeCacheSpecificKey(...PartsCacher.getPartsFromKey(partKey))
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async cacheDBMissesAsync(partsList: readonly Parts[]): Promise<void> {
|
|
||||||
await this.cacher.cacheDBMissesAsync(
|
|
||||||
partsList.map((parts) => this.makeCacheSpecificKey(...parts))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async invalidateManyAsync(partsList: readonly Parts[]): Promise<void> {
|
|
||||||
await this.cacher.invalidateManyAsync(
|
|
||||||
partsList.map((parts) => this.makeCacheSpecificKey(...parts))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -45,11 +45,6 @@ export { default as IEntityCacheAdapterProvider } from './IEntityCacheAdapterPro
|
|||||||
export { default as IEntityDatabaseAdapterProvider } from './IEntityDatabaseAdapterProvider';
|
export { default as IEntityDatabaseAdapterProvider } from './IEntityDatabaseAdapterProvider';
|
||||||
export { default as EntityQueryContextProvider } from './EntityQueryContextProvider';
|
export { default as EntityQueryContextProvider } from './EntityQueryContextProvider';
|
||||||
export { default as IEntityGenericCacher } from './IEntityGenericCacher';
|
export { default as IEntityGenericCacher } from './IEntityGenericCacher';
|
||||||
export { default as SimplePartsCacher } from './SimplePartsCacher';
|
|
||||||
export { default as PartsCacher } from './PartsCacher';
|
|
||||||
export * from './PartsCacher';
|
|
||||||
export { default as PartsCacheAdapter } from './PartsCacheAdapter';
|
|
||||||
export { default as ComposedPartsCacher } from './ComposedPartsCacher';
|
|
||||||
export { default as ReadonlyEntity } from './ReadonlyEntity';
|
export { default as ReadonlyEntity } from './ReadonlyEntity';
|
||||||
export { default as ViewerContext } from './ViewerContext';
|
export { default as ViewerContext } from './ViewerContext';
|
||||||
export { default as ViewerScopedEntityCompanion } from './ViewerScopedEntityCompanion';
|
export { default as ViewerScopedEntityCompanion } from './ViewerScopedEntityCompanion';
|
||||||
|
|||||||
Reference in new issue
Block a user