Skip to content

Commit

Permalink
Add feature to retrieve a value from the cache without revalidation (#41
Browse files Browse the repository at this point in the history
)

* 3.3.0

* Add feature to retrieve a value from the cache without revalidation

* Update changelog and readme for v3.4.0
  • Loading branch information
jperasmus committed Mar 21, 2024
1 parent 24e0583 commit 90b0d50
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 12 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

...

## [3.4.0] - 2024-03-21

### Added

- Expose a `.retrieve()` convenience method to simply read from the cache without revalidation.

## [3.3.0] - 2024-02-15

### Added
Expand Down Expand Up @@ -106,7 +112,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Add emitter events for when storage get and set fails

[unreleased]: https://github.com/jperasmus/stale-while-revalidate-cache/compare/v3.3.0...HEAD
[unreleased]: https://github.com/jperasmus/stale-while-revalidate-cache/compare/v3.4.0...HEAD
[3.4.0]: https://github.com/jperasmus/stale-while-revalidate-cache/compare/v3.3.0...v3.4.0
[3.3.0]: https://github.com/jperasmus/stale-while-revalidate-cache/compare/v3.2.1...v3.3.0
[3.2.1]: https://github.com/jperasmus/stale-while-revalidate-cache/compare/v3.2.0...v3.2.1
[3.2.0]: https://github.com/jperasmus/stale-while-revalidate-cache/compare/v3.1.3...v3.2.0
Expand Down
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,18 @@ const result = await swr.persist(cacheKey, cacheValue)

The value will be passed through the `serialize` method you optionally provided when you instantiated the `swr` helper.

#### Manually read from cache

There is a convenience static method made available if you need to simply read from the underlying storage without triggering revalidation. Sometimes you just want to know if there is a value in the cache for a given key.

```typescript
const cacheKey = 'your-cache-key'

const resultPayload = await swr.retrieve(cacheKey)
```

The cached value will be passed through the `deserialize` method you optionally provided when you instantiated the `swr` helper.

#### Manually delete from cache

There is a convenience static method made available if you need to manually delete a cache entry from the underlying storage.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "3.2.1",
"version": "3.3.0",
"name": "stale-while-revalidate-cache",
"license": "MIT",
"main": "dist/index.js",
Expand Down
43 changes: 43 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -697,4 +697,47 @@ describe('createStaleWhileRevalidateCache', () => {
expect(mockedLocalStorage.getItem(createTimeCacheKey(key))).toEqual(null)
})
})

describe('swr.retrieve()', () => {
it('should return the cache value and metadata for given key', async () => {
const swr = createStaleWhileRevalidateCache(validConfig)

const key = 'retrieve key'
const value = 'value'

const now = Date.now()

// Manually set the value in the cache
await Promise.all([
validConfig.storage.setItem(key, value),
validConfig.storage.setItem(
createTimeCacheKey(key),
(now + 10000).toString() // 10 seconds in the future
),
])

const result = await swr.retrieve(key)

expect(result).toMatchObject({
cachedValue: value,
cachedAge: expect.any(Number),
cachedAt: expect.any(Number),
now: expect.any(Number),
})
})

it('should return null for cachedValue if the cache value is not found', async () => {
const swr = createStaleWhileRevalidateCache(validConfig)

const key = 'retrieve missing key'

const result = await swr.retrieve(key)

expect(result).toMatchObject({
cachedValue: null,
cachedAge: 0,
now: expect.any(Number),
})
})
})
})
62 changes: 54 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
Config,
IncomingCacheKey,
ResponseEnvelope,
RetrieveCachedValueResponse,
StaleWhileRevalidateCache,
StaticMethods,
} from '../types'
Expand Down Expand Up @@ -82,6 +83,45 @@ export function createStaleWhileRevalidateCache(
}
}

async function retrieveValue<CacheValue>({
cacheKey,
storage,
deserialize,
}: {
cacheKey: IncomingCacheKey
storage: Config['storage']
deserialize: NonNullable<Config['deserialize']>
}): Promise<RetrieveCachedValueResponse<CacheValue>> {
const now = Date.now()
const key = getCacheKey(cacheKey)
const timeKey = createTimeCacheKey(key)

try {
const [cachedValue, cachedAt] = await Promise.all([
storage.getItem(key),
storage.getItem(timeKey),
])

if (
isNil(cachedValue) ||
isNil(cachedAt) ||
Number.isNaN(Number(cachedAt))
) {
return { cachedValue: null, cachedAge: 0, now }
}

return {
cachedValue: deserialize(cachedValue) as CacheValue | null,
cachedAge: now - Number(cachedAt),
cachedAt: Number(cachedAt),
now,
}
} catch (error) {
emitter.emit(EmitterEvents.cacheGetFailed, { cacheKey, error })
throw error
}
}

async function staleWhileRevalidate<CacheValue>(
cacheKey: IncomingCacheKey,
fn: () => CacheValue | Promise<CacheValue>,
Expand All @@ -106,13 +146,6 @@ export function createStaleWhileRevalidateCache(
let invocationCount = 0
let cacheStatus: CacheStatus = CacheResponseStatus.MISS

type RetrieveCachedValueResponse = Promise<{
cachedValue: unknown | null
cachedAge: number
cachedAt?: number
now: number
}>

if (inFlightKeys.has(key)) {
emitter.emit(EmitterEvents.cacheInFlight, { key, cacheKey })

Expand All @@ -137,7 +170,9 @@ export function createStaleWhileRevalidateCache(

inFlightKeys.add(key)

async function retrieveCachedValue(): RetrieveCachedValueResponse {
async function retrieveCachedValue(): Promise<
RetrieveCachedValueResponse<unknown>
> {
const now = Date.now()

try {
Expand Down Expand Up @@ -290,8 +325,19 @@ export function createStaleWhileRevalidateCache(
})
}

const retrieve: StaticMethods['retrieve'] = async <CacheValue>(
cacheKey: IncomingCacheKey
) => {
return retrieveValue<CacheValue>({
cacheKey,
storage: cacheConfig.storage,
deserialize: cacheConfig.deserialize,
})
}

staleWhileRevalidate.delete = del
staleWhileRevalidate.persist = persist
staleWhileRevalidate.retrieve = retrieve

return extendWithEmitterMethods(emitter, staleWhileRevalidate)
}
10 changes: 10 additions & 0 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ export type IncomingCacheKey = string | (() => string)

export type CacheStatus = 'fresh' | 'stale' | 'expired' | 'miss'

export type RetrieveCachedValueResponse<CacheValue> = {
cachedValue: CacheValue | null
cachedAge: number
cachedAt?: number
now: number
}

export type ResponseEnvelope<CacheValue> = {
value: CacheValue
status: CacheStatus
Expand All @@ -43,6 +50,9 @@ export type StaleWhileRevalidateCache = <CacheValue>(

export type StaticMethods = {
delete: (cacheKey: IncomingCacheKey) => Promise<void>
retrieve: <CacheValue>(
cacheKey: IncomingCacheKey
) => Promise<RetrieveCachedValueResponse<CacheValue>>
persist: <CacheValue>(
cacheKey: IncomingCacheKey,
cacheValue: CacheValue
Expand Down

0 comments on commit 90b0d50

Please sign in to comment.