Compare commits

..
7 changed files with 42 additions and 30 deletions

No files matched your search

@@ -1,4 +1,4 @@
import { CacheLoadResult, CacheStatus } from '@expo/entity';
import { CacheLoadResult, CacheStatus, IEntityGenericCacher } from '@expo/entity';
import LRUCache from 'lru-cache';
// Sentinel value we store in local memory to negatively cache a database miss.
@@ -77,8 +77,8 @@ type LRUCacheOptionsV7<K, V> = {
updateRecencyOnHas?: boolean;
};
export default class GenericLocalMemoryCacher<TFields> {
constructor(private readonly lruCache: LocalMemoryCache<TFields>) {}
export default class GenericLocalMemoryCacher<TFields> implements IEntityGenericCacher<TFields> {
constructor(private readonly localMemoryCache: LocalMemoryCache<TFields>) {}
static createLRUCache<TFields>(
options: { maxSize?: number; ttlSeconds?: number } = {}
@@ -95,7 +95,7 @@ export default class GenericLocalMemoryCacher<TFields> {
return new LRUCache<string, LocalMemoryCacheValue<TFields>>(lruCacheOptions as any);
}
static createNoOpLRUCache<TFields>(): LocalMemoryCache<TFields> {
static createNoOpCache<TFields>(): LocalMemoryCache<TFields> {
return new LRUCache<string, LocalMemoryCacheValue<TFields>>({
max: 0,
maxAge: -1,
@@ -107,7 +107,7 @@ export default class GenericLocalMemoryCacher<TFields> {
): Promise<ReadonlyMap<string, CacheLoadResult<TFields>>> {
const cacheResults = new Map<string, CacheLoadResult<TFields>>();
for (const key of keys) {
const cacheResult = this.lruCache.get(key);
const cacheResult = this.localMemoryCache.get(key);
if (cacheResult === DOES_NOT_EXIST_LOCAL_MEMORY_CACHE) {
cacheResults.set(key, {
status: CacheStatus.NEGATIVE,
@@ -128,19 +128,19 @@ export default class GenericLocalMemoryCacher<TFields> {
public async cacheManyAsync(objectMap: ReadonlyMap<string, Readonly<TFields>>): Promise<void> {
for (const [key, item] of objectMap) {
this.lruCache.set(key, item);
this.localMemoryCache.set(key, item);
}
}
public async cacheDBMissesAsync(keys: string[]): Promise<void> {
public async cacheDBMissesAsync(keys: readonly string[]): Promise<void> {
for (const key of keys) {
this.lruCache.set(key, DOES_NOT_EXIST_LOCAL_MEMORY_CACHE);
this.localMemoryCache.set(key, DOES_NOT_EXIST_LOCAL_MEMORY_CACHE);
}
}
public async invalidateManyAsync(keys: string[]): Promise<void> {
public async invalidateManyAsync(keys: readonly string[]): Promise<void> {
for (const key of keys) {
this.lruCache.del(key);
this.localMemoryCache.del(key);
}
}
@@ -8,10 +8,10 @@ export default class LocalMemoryCacheAdapter<TFields> extends EntityCacheAdapter
constructor(
entityConfiguration: EntityConfiguration<TFields>,
lruCache: LocalMemoryCache<TFields>
localMemoryCache: LocalMemoryCache<TFields>
) {
super(entityConfiguration);
this.genericLocalMemoryCacher = new GenericLocalMemoryCacher(lruCache);
this.genericLocalMemoryCacher = new GenericLocalMemoryCacher(localMemoryCache);
}
public async loadManyAsync<N extends keyof TFields>(
@@ -29,7 +29,7 @@ export default class LocalMemoryCacheAdapter<TFields> extends EntityCacheAdapter
const fieldValue = localMemoryCacheKeyToFieldValueMapping.get(cacheKey);
invariant(
fieldValue !== undefined,
'Unspecified cache key %s returned from generic Local Memory cacher',
'Unspecified cache key %s returned from generic local memory cacher',
cacheKey
);
return fieldValue;
@@ -8,13 +8,13 @@ import {
import GenericLocalMemoryCacher, { LocalMemoryCache } from './GenericLocalMemoryCacher';
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
static localMemoryCacheAdapterMap = new Map<string, LocalMemoryCacheAdapter<any>>();
static getNoOpProvider(): IEntityCacheAdapterProvider {
return new LocalMemoryCacheAdapterProvider(<TFields>() =>
GenericLocalMemoryCacher.createNoOpLRUCache<TFields>()
GenericLocalMemoryCacher.createNoOpCache<TFields>()
);
}
@@ -26,7 +26,9 @@ export class LocalMemoryCacheAdapterProvider implements IEntityCacheAdapterProvi
);
}
private constructor(private readonly lruCacheCreator: <TFields>() => LocalMemoryCache<TFields>) {}
private constructor(
private readonly localMemoryCacheCreator: <TFields>() => LocalMemoryCache<TFields>
) {}
public getCacheAdapter<TFields>(
entityConfiguration: EntityConfiguration<TFields>
@@ -35,8 +37,8 @@ export class LocalMemoryCacheAdapterProvider implements IEntityCacheAdapterProvi
LocalMemoryCacheAdapterProvider.localMemoryCacheAdapterMap,
entityConfiguration.tableName,
() => {
const lruCache = this.lruCacheCreator<TFields>();
return new LocalMemoryCacheAdapter(entityConfiguration, lruCache);
const localMemoryCache = this.localMemoryCacheCreator<TFields>();
return new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
}
);
}
@@ -64,16 +64,16 @@ describe(LocalMemoryCacheAdapter, () => {
describe('cacheManyAsync', () => {
it('correctly caches all objects', async () => {
const lruCache = GenericLocalMemoryCacher.createLRUCache<BlahFields>({
const localMemoryCache = GenericLocalMemoryCacher.createLRUCache<BlahFields>({
maxSize: Number.MAX_SAFE_INTEGER,
ttlSeconds: Number.MAX_SAFE_INTEGER,
});
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, lruCache);
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
await cacheAdapter.cacheManyAsync('id', new Map([['test-id-1', { id: 'test-id-1' }]]));
const cacheKey = cacheAdapter['makeCacheKey']('id', 'test-id-1');
expect(lruCache.get(cacheKey)).toMatchObject({
expect(localMemoryCache.get(cacheKey)).toMatchObject({
id: 'test-id-1',
});
});
@@ -81,27 +81,27 @@ describe(LocalMemoryCacheAdapter, () => {
describe('cacheDBMissesAsync', () => {
it('correctly caches misses', async () => {
const lruCache = GenericLocalMemoryCacher.createLRUCache<BlahFields>({
const localMemoryCache = GenericLocalMemoryCacher.createLRUCache<BlahFields>({
maxSize: Number.MAX_SAFE_INTEGER,
ttlSeconds: Number.MAX_SAFE_INTEGER,
});
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, lruCache);
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
await cacheAdapter.cacheDBMissesAsync('id', ['test-id-1']);
const cacheKey = cacheAdapter['makeCacheKey']('id', 'test-id-1');
expect(lruCache.get(cacheKey)).toEqual(DOES_NOT_EXIST_LOCAL_MEMORY_CACHE);
expect(localMemoryCache.get(cacheKey)).toEqual(DOES_NOT_EXIST_LOCAL_MEMORY_CACHE);
});
});
describe('invalidateManyAsync', () => {
it('invalidates correctly', async () => {
const lruCache = GenericLocalMemoryCacher.createLRUCache<BlahFields>({
const localMemoryCache = GenericLocalMemoryCacher.createLRUCache<BlahFields>({
maxSize: Number.MAX_SAFE_INTEGER,
ttlSeconds: Number.MAX_SAFE_INTEGER,
});
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, lruCache);
const cacheAdapter = new LocalMemoryCacheAdapter(entityConfiguration, localMemoryCache);
await cacheAdapter.cacheManyAsync('id', new Map([['test-id-1', { id: 'test-id-1' }]]));
await cacheAdapter.cacheDBMissesAsync('id', ['test-id-2']);
await cacheAdapter.invalidateManyAsync('id', ['test-id-1', 'test-id-2']);
@@ -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';
@@ -97,7 +97,7 @@ export default class GenericRedisCacher<TFields> implements IEntityGenericCacher
await wrapNativeRedisCallAsync(() => redisTransaction.exec());
}
public async cacheDBMissesAsync(keys: string[]): Promise<void> {
public async cacheDBMissesAsync(keys: readonly string[]): Promise<void> {
if (keys.length === 0) {
return;
}
@@ -114,7 +114,7 @@ export default class GenericRedisCacher<TFields> implements IEntityGenericCacher
await wrapNativeRedisCallAsync(() => redisTransaction.exec());
}
public async invalidateManyAsync(keys: string[]): Promise<void> {
public async invalidateManyAsync(keys: readonly string[]): Promise<void> {
if (keys.length === 0) {
return;
}
+2 -2
View File
@@ -9,7 +9,7 @@ export default interface IEntityGenericCacher<TFields> {
cacheManyAsync(objectMap: ReadonlyMap<string, Readonly<TFields>>): Promise<void>;
cacheDBMissesAsync(keys: string[]): Promise<void>;
cacheDBMissesAsync(keys: readonly string[]): Promise<void>;
invalidateManyAsync(keys: string[]): Promise<void>;
invalidateManyAsync(keys: readonly string[]): Promise<void>;
}