Add caching of user-data (#568)

* Add caching of user-data for 600 seconds

* Make cache-entry interface commonly usable

* Extract revision types

* Remove revision-cache rule

* Use seconds as cache-time interval (Date.now uses milliseconds)

* Fix import error

* Extract cache logic into common cache-class

* Add cache class that was forgotten to commit in last commit

* Start adding unit tests

* Fix bug detected during unit-testing

* Add unit tests for cache

* Made entry-limit test more explicit

* Renamed files to lower-case starting letter
This commit is contained in:
Erik Michelson 2020-09-30 23:37:57 +02:00 committed by GitHub
parent 0f31c3b0b4
commit 091b225672
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 156 additions and 31 deletions

View file

@ -0,0 +1,77 @@
import { Cache } from './cache'
describe('Test caching functionality', () => {
let testCache: Cache<string, number>
beforeEach(() => {
testCache = new Cache<string, number>(1000)
})
it('initialize with right lifetime, no entry limit', () => {
const lifetime = 1000
const lifetimedCache = new Cache<string, string>(lifetime)
expect(lifetimedCache.entryLifetime).toEqual(lifetime)
expect(lifetimedCache.maxEntries).toEqual(0)
})
it('initialize with right lifetime, given entry limit', () => {
const lifetime = 1000
const maxEntries = 10
const limitedCache = new Cache<string, string>(lifetime, maxEntries)
expect(limitedCache.entryLifetime).toEqual(lifetime)
expect(limitedCache.maxEntries).toEqual(maxEntries)
})
it('entry exists after inserting', () => {
testCache.put('test', 123)
expect(testCache.has('test')).toBe(true)
})
it('entry does not exist prior inserting', () => {
expect(testCache.has('test')).toBe(false)
})
it('entry does expire', () => {
const shortLivingCache = new Cache<string, number>(2)
shortLivingCache.put('test', 123)
expect(shortLivingCache.has('test')).toBe(true)
setTimeout(() => {
expect(shortLivingCache.has('test')).toBe(false)
}, 2000)
})
it('entry value does not change', () => {
const testValue = Date.now()
testCache.put('test', testValue)
expect(testCache.get('test')).toEqual(testValue)
})
it('error is thrown on non-existent entry', () => {
const accessNonExistentEntry = () => {
testCache.get('test')
}
expect(accessNonExistentEntry).toThrow(Error)
})
it('newer item replaces older item', () => {
testCache.put('test', 123)
testCache.put('test', 456)
expect(testCache.get('test')).toEqual(456)
})
it('entry limit is respected', () => {
const limitedCache = new Cache<string, number>(1000, 2)
limitedCache.put('first', 1)
expect(limitedCache.has('first')).toBe(true)
expect(limitedCache.has('second')).toBe(false)
expect(limitedCache.has('third')).toBe(false)
limitedCache.put('second', 2)
expect(limitedCache.has('first')).toBe(true)
expect(limitedCache.has('second')).toBe(true)
expect(limitedCache.has('third')).toBe(false)
limitedCache.put('third', 3)
expect(limitedCache.has('first')).toBe(false)
expect(limitedCache.has('second')).toBe(true)
expect(limitedCache.has('third')).toBe(true)
})
})

45
src/components/common/cache/cache.ts vendored Normal file
View file

@ -0,0 +1,45 @@
export interface CacheEntry<T> {
entryCreated: number
data: T
}
export class Cache<K, V> {
private store = new Map<K, CacheEntry<V>>()
readonly entryLifetime: number
readonly maxEntries: number
constructor (lifetime: number, maxEntries = 0) {
if (lifetime < 0) {
throw new Error('Cache entry lifetime can not be less than 0 seconds.')
}
this.entryLifetime = lifetime
this.maxEntries = maxEntries
}
has (key: K): boolean {
if (!this.store.has(key)) {
return false
}
const entry = this.store.get(key)
return (!!entry && entry.entryCreated >= (Date.now() - this.entryLifetime * 1000))
}
get (key: K): V {
const entry = this.store.get(key)
if (!entry) {
throw new Error('This cache entry does not exist. Check with ".has()" before using ".get()".')
}
return entry.data
}
put (key: K, value: V): void {
if (this.maxEntries > 0 && this.store.size === this.maxEntries) {
this.store.delete(this.store.keys().next().value)
}
this.store.set(key, {
entryCreated: Date.now(),
data: value
})
}
}