Skip to content

Commit

Permalink
Switch to using toad-cache (#232)
Browse files Browse the repository at this point in the history
  • Loading branch information
kibertoad committed Apr 7, 2023
1 parent bc373ca commit 7f78b91
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 21 deletions.
22 changes: 14 additions & 8 deletions lib/memory/InMemoryCache.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
import type { LRU } from 'tiny-lru'
import { lru } from 'tiny-lru'
import type { LRU } from 'toad-cache'
import { fifo, lru, ToadCache } from 'toad-cache'
import { SynchronousCache, SynchronousGroupedCache } from '../types/SyncDataSources'
import { CacheConfiguration } from '../types/DataSources'

export interface InMemoryCacheConfiguration extends CacheConfiguration {
cacheType?: 'lru' | 'fifo'
maxItems?: number
maxGroups?: number
maxItemsPerGroup?: number
}

const DEFAULT_CONFIGURATION = {
cacheType: 'lru',
maxItems: 500,
maxGroups: 1000,
maxItemsPerGroup: 500,
} satisfies Omit<InMemoryCacheConfiguration, 'ttlInMsecs'>

export class InMemoryCache<T> implements SynchronousCache<T>, SynchronousGroupedCache<T> {
private readonly cache: LRU<T | null>
private readonly groups: LRU<LRU<T | null> | undefined | null>
private readonly cache: ToadCache<T | null>
private readonly groups: LRU<ToadCache<T | null> | undefined | null>
private readonly maxItemsPerGroup: number
name = 'In-memory cache'
private readonly ttlInMsecs: number | undefined
public readonly ttlLeftBeforeRefreshInMsecs?: number
private readonly cacheConstructor: <T = any>(max?: number, ttl?: number) => ToadCache<T>

constructor(config: InMemoryCacheConfiguration) {
this.cache = lru(config.maxItems ?? DEFAULT_CONFIGURATION.maxItems, config.ttlInMsecs ?? 0, true)
const cacheType = config.cacheType ?? DEFAULT_CONFIGURATION.cacheType
this.cacheConstructor = cacheType === 'fifo' ? fifo : lru

this.cache = this.cacheConstructor(config.maxItems ?? DEFAULT_CONFIGURATION.maxItems, config.ttlInMsecs ?? 0)
this.groups = lru(config.maxGroups ?? DEFAULT_CONFIGURATION.maxGroups)
this.maxItemsPerGroup = config.maxItemsPerGroup ?? DEFAULT_CONFIGURATION.maxItemsPerGroup
this.ttlInMsecs = config.ttlInMsecs
Expand All @@ -37,7 +43,7 @@ export class InMemoryCache<T> implements SynchronousCache<T>, SynchronousGrouped
return groupCache
}

const newGroupCache = lru(this.maxItemsPerGroup, this.ttlInMsecs)
const newGroupCache = this.cacheConstructor(this.maxItemsPerGroup, this.ttlInMsecs)
this.groups.set(groupId, newGroupCache)
return newGroupCache
}
Expand All @@ -52,7 +58,7 @@ export class InMemoryCache<T> implements SynchronousCache<T>, SynchronousGrouped
}
setForGroup(key: string, value: T | null, groupId: string) {
const group = this.resolveGroup(groupId)
group.set(key, value, false, true)
group.set(key, value)
}

deleteFromGroup(key: string, groupId: string): void {
Expand Down Expand Up @@ -83,6 +89,6 @@ export class InMemoryCache<T> implements SynchronousCache<T>, SynchronousGrouped
}

set(key: string, value: T | null): void {
this.cache.set(key, value, false, true)
this.cache.set(key, value)
}
}
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,20 @@
"alternate",
"source",
"redis",
"memory"
"memory",
"fifo",
"lru"
],
"homepage": "https://github.com/kibertoad/layered-loader",
"dependencies": {
"tiny-lru": "^10.3.0"
"toad-cache": "^1.0.0"
},
"devDependencies": {
"@types/jest": "^29.5.0",
"@types/node": "^18.15.10",
"@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0",
"eslint": "^8.36.0",
"eslint": "^8.37.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-prettier": "^4.2.1",
Expand Down
62 changes: 53 additions & 9 deletions test/InMemoryCache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,46 @@ const IN_MEMORY_CACHE_CONFIG = { ttlInMsecs: 999 } satisfies InMemoryCacheConfig

describe('InMemoryCache', () => {
describe('set', () => {
it('sets value after group has already expired', () => {
it('expires LRU', () => {
const cache = new InMemoryCache({
maxGroups: 1,
cacheType: 'lru',
maxItems: 2,
ttlInMsecs: 1,
})
cache.setForGroup('key', 'value', 'group')
cache.setForGroup('key', 'value', 'group2')
cache.set('key', 'value')
cache.set('key2', 'value2')

const preValue = cache.getFromGroup('key', 'group')
expect(preValue).toBeUndefined()
cache.get('key')
cache.set('key3', 'value3')

cache.setForGroup('key', 'value', 'group')
const postValue = cache.getFromGroup('key', 'group')
expect(postValue).toBe('value')
const value1 = cache.get('key')
const value2 = cache.get('key2')
const value3 = cache.get('key3')

expect(value1).toBe('value')
expect(value2).toBeUndefined()
expect(value3).toBe('value3')
})

it('expires FIFO', () => {
const cache = new InMemoryCache({
cacheType: 'fifo',
maxItems: 2,
ttlInMsecs: 1,
})
cache.set('key', 'value')
cache.set('key2', 'value2')

cache.get('key')
cache.set('key3', 'value3')

const value1 = cache.get('key')
const value2 = cache.get('key2')
const value3 = cache.get('key3')

expect(value1).toBeUndefined()
expect(value2).toBe('value2')
expect(value3).toBe('value3')
})

it('defaults to infinite ttl', async () => {
Expand All @@ -33,6 +59,24 @@ describe('InMemoryCache', () => {
})
})

describe('setForGroup', () => {
it('sets value after group has already expired', () => {
const cache = new InMemoryCache({
maxGroups: 1,
ttlInMsecs: 1,
})
cache.setForGroup('key', 'value', 'group')
cache.setForGroup('key', 'value', 'group2')

const preValue = cache.getFromGroup('key', 'group')
expect(preValue).toBeUndefined()

cache.setForGroup('key', 'value', 'group')
const postValue = cache.getFromGroup('key', 'group')
expect(postValue).toBe('value')
})
})

describe('getExpirationTime', () => {
it('returns undefined for non-existent entry', () => {
const cache = new InMemoryCache(IN_MEMORY_CACHE_CONFIG)
Expand Down
7 changes: 7 additions & 0 deletions test/LoadingOperation-main.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ describe('LoadingOperation', () => {
expect(operation.getInMemoryOnly('key')).toBe('value')
await Promise.resolve()
expect(loader.counter).toBe(2)

// FIXME these promise.resolve weren't needed, until it suddenly stopped failing without any changes. Need to investigate, what is going on.
await Promise.resolve()
await Promise.resolve()
await Promise.resolve()
await Promise.resolve()
await Promise.resolve()
// @ts-ignore
const expirationTimePost = operation.inMemoryCache.getExpirationTime('key')

Expand Down

0 comments on commit 7f78b91

Please sign in to comment.