Skip to content

Commit

Permalink
feat(cache) timer ttl eviction (Kong#52)
Browse files Browse the repository at this point in the history
* automatic garbage collection of refs
* fixes issue  Kong#51 with low ttl causing infinite isValidating
* ascii art

Fixes Kong#51
  • Loading branch information
darrenjennings authored Jun 27, 2020
1 parent 534acc2 commit 06b5249
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 39 deletions.
33 changes: 11 additions & 22 deletions src/lib/cache.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
interface ICacheItem {
data: any,
createdAt: number
createdAt: number,
expiresAt: number
}

export default class SWRVCache {
Expand All @@ -15,37 +16,25 @@ export default class SWRVCache {
/**
* Get cache item while evicting
*/
get (k: string, ttl: number): ICacheItem {
this.shift(ttl)
get (k: string): ICacheItem {
return this.items.get(k)
}

set (k: string, v: any) {
const item: ICacheItem = {
set (k: string, v: any, ttl: number) {
const timeToLive = ttl || this.ttl
const now = Date.now()
const item = {
data: v,
createdAt: Date.now()
createdAt: now,
expiresAt: timeToLive ? now + timeToLive : Infinity
}

timeToLive && setTimeout(() => this.delete(k), timeToLive)

this.items.set(k, item)
}

delete (k: string) {
this.items.delete(k)
}

/**
* Eviction of cache items based on some ttl of ICacheItem.createdAt
*/
private shift (ttl: number) {
const timeToLive = ttl || this.ttl
if (!timeToLive) {
return
}

this.items.forEach((v, k) => {
if (v.createdAt < (Date.now() - timeToLive)) {
this.items.delete(k)
}
})
}
}
48 changes: 35 additions & 13 deletions src/use-swrv.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
import { reactive,
/** ____
*--------------/ \.------------------/
* / swrv \. / //
* / / /\. / //
* / _____/ / \. /
* / / ____/ . \. /
* / \ \_____ \. /
* / . \_____ \ \ / //
* \ _____/ / ./ / //
* \ / _____/ ./ /
* \ / / . ./ /
* \ / / ./ /
* . \/ ./ / //
* \ ./ / //
* \.. / /
* . ||| /
* ||| /
* . ||| / //
* ||| / //
* ||| /
*/
import {
reactive,
watch,
ref,
toRefs,
Expand Down Expand Up @@ -31,20 +53,21 @@ const defaultConfig: IConfig = {
* Cache the refs for later revalidation
*/
function setRefCache (key, theRef, ttl) {
const refCacheItem = REF_CACHE.get(key, ttl)
const refCacheItem = REF_CACHE.get(key)
if (refCacheItem) {
refCacheItem.data.push(theRef)
} else {
REF_CACHE.set(key, [theRef])
// #51 ensures ref cache does not evict too soon
const gracePeriod = 5000
REF_CACHE.set(key, [theRef], ttl + gracePeriod)
}
}

/**
* Main mutation function for receiving data from promises to change state and
* set data cache
*/
const mutate = async <Data>(key: string, res: Promise<Data> | Data,
cache = DATA_CACHE, ttl = defaultConfig.ttl) => {
const mutate = async <Data>(key: string, res: Promise<Data> | Data, cache = DATA_CACHE, ttl = defaultConfig.ttl) => {
let data, error, isValidating

if (isPromise(res)) {
Expand All @@ -61,13 +84,13 @@ const mutate = async <Data>(key: string, res: Promise<Data> | Data,

const newData = { data, error, isValidating }
if (typeof data !== 'undefined') {
cache.set(key, newData)
cache.set(key, newData, ttl)
}

/**
* Revalidate all swrv instances with new data
*/
const stateRef = REF_CACHE.get(key, ttl)
const stateRef = REF_CACHE.get(key)
if (stateRef && stateRef.data.length) {
// This filter fixes #24 race conditions to only update ref data of current
// key, while data cache will continue to be updated if revalidation is
Expand Down Expand Up @@ -155,7 +178,7 @@ export default function useSWRV<Data = any, Error = any> (key: IKey, fn?: fetche
const revalidate = async () => {
const keyVal = keyRef.value
if (!isDocumentVisible()) { return }
const cacheItem = config.cache.get(keyVal, ttl)
const cacheItem = config.cache.get(keyVal)
let newData = cacheItem && cacheItem.data

stateRef.isValidating = true
Expand All @@ -165,18 +188,17 @@ export default function useSWRV<Data = any, Error = any> (key: IKey, fn?: fetche
}

if (!fn) return
/**
* Currently getter's of SWRVCache will evict
*/

const trigger = async () => {
const promiseFromCache = PROMISES_CACHE.get(keyVal, config.dedupingInterval)
const promiseFromCache = PROMISES_CACHE.get(keyVal)
if (!promiseFromCache) {
const newPromise = fn(keyVal)
PROMISES_CACHE.set(keyVal, newPromise)
PROMISES_CACHE.set(keyVal, newPromise, config.dedupingInterval)
await mutate(keyVal, newPromise, config.cache, ttl)
} else {
await mutate(keyVal, promiseFromCache.data, config.cache, ttl)
}
stateRef.isValidating = false
}

if (newData && config.revalidateDebounce) {
Expand Down
48 changes: 44 additions & 4 deletions tests/use-swrv.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,50 @@ describe('useSWRV', () => {
expect(invoked).toBe(1) // empty fetcher is OK
done()
})

it('should return data even when cache ttl expires during request', async done => {
const loadData = () => new Promise(res => setTimeout(() => res('data'), 100))
let mutate
const vm = new Vue({
render: h => h(defineComponent({
template: `<div>hello, {{data}}, {{isValidating ? 'loading' : 'ready'}}</div>`,
setup () {
const { data, isValidating, revalidate } = useSWRV('is-validating-3', loadData, {
ttl: 50
})

mutate = revalidate
console.log(mutate)
return {
data,
isValidating
}
}
}))
}).$mount()

timeout(75)
await tick(vm, 2)
expect(vm.$el.textContent).toBe('hello, , loading')

timeout(25)
await tick(vm, 2)
expect(vm.$el.textContent).toBe('hello, data, ready')

mutate()
await tick(vm, 2)
expect(vm.$el.textContent).toBe('hello, data, loading')
timeout(25)
mutate()
await tick(vm, 2)
expect(vm.$el.textContent).toBe('hello, data, loading')

mutate()
timeout(100)
await tick(vm, 2)
expect(vm.$el.textContent).toBe('hello, data, ready')
done()
})
})

describe('useSWRV - loading', () => {
Expand Down Expand Up @@ -363,7 +407,6 @@ describe('useSWRV - loading', () => {
})

it('should return loading state via isValidating', async done => {
// Prime the cache
const vm = new Vue({
render: h => h(defineComponent({
setup () {
Expand All @@ -387,9 +430,6 @@ describe('useSWRV - loading', () => {
await tick(vm, 2)
expect(vm.$el.textContent).toBe('hello, data, loading')

timeout(100)
await tick(vm, 2)
expect(vm.$el.textContent).toBe('hello, data, ready')
done()
})
})
Expand Down

0 comments on commit 06b5249

Please sign in to comment.