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