Skip to content

Commit

Permalink
feat: 🎸 enable setting cache ttl from promise response (#607)
Browse files Browse the repository at this point in the history
* chore: 🤖 fix test case name

* feat: 🎸 enable setting cache ttl from promise response
  • Loading branch information
rafaelromon authored Oct 31, 2024
1 parent bafae9c commit 6d40aae
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilly-waves-impress.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sebspark/promise-cache": minor
---

enable setting cache ttl from promise response
79 changes: 78 additions & 1 deletion packages/promise-cache/src/promiseCache.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,83 @@ describe('PromiseCache', () => {
expect(mockDelegate).toHaveBeenCalledTimes(2)
})

it('should get ttl from response if key is provided', async () => {
const mockDelegate = vi.fn()

mockDelegate.mockResolvedValue({
value: 42,
ttl: '112312',
})

const mockedPersistorSet = vi.spyOn(cache.persistor, 'set')
await cache.wrap('testKey4', mockDelegate, undefined, 'ttl')

// Cache should be set with the TTL from the response
expect(mockedPersistorSet).toHaveBeenCalledWith('testkey4', {
timestamp: expect.any(Number),
ttl: 112312,
value: {
value: 42,
ttl: '112312',
},
})
})

it('should ignore ttl from response if parse fails', async () => {
const mockDelegate = vi.fn()

mockDelegate.mockResolvedValue({
value: 42,
ttl: '112adsa3a12',
})

const mockedPersistorSet = vi.spyOn(cache.persistor, 'set')
await cache.wrap('testKey5', mockDelegate, undefined, 'ttl')

expect(mockedPersistorSet).toHaveBeenCalledWith('testkey5', {
timestamp: expect.any(Number),
ttl: 1000,
value: {
value: 42,
ttl: '112adsa3a12',
},
})
})

it('should ignore ttl from response if key is not present', async () => {
const mockDelegate = vi.fn()

mockDelegate.mockResolvedValue({
value: 42,
})

const mockedPersistorSet = vi.spyOn(cache.persistor, 'set')
await cache.wrap('testKey6', mockDelegate, undefined, 'ttl')

expect(mockedPersistorSet).toHaveBeenCalledWith('testkey6', {
timestamp: expect.any(Number),
ttl: 1000,
value: {
value: 42,
},
})
})

it('should ignore ttl from response if response is not an object', async () => {
const mockDelegate = vi.fn()

mockDelegate.mockResolvedValue(42)

const mockedPersistorSet = vi.spyOn(cache.persistor, 'set')
await cache.wrap('testKey7', mockDelegate, undefined, 'ttl')

expect(mockedPersistorSet).toHaveBeenCalledWith('testkey7', {
timestamp: expect.any(Number),
ttl: 1000,
value: 42,
})
})

it('should not differentiate between keys with different casing', async () => {
const mockDelegate1 = vi.fn()
const mockDelegate2 = vi.fn()
Expand All @@ -148,7 +225,7 @@ describe('PromiseCache', () => {
expect(value1 === value2).toBeTruthy()
})

it('should not differentiate between keys with different casing', async () => {
it('should differentiate between keys with different casing', async () => {
const mockDelegate1 = vi.fn()
const mockDelegate2 = vi.fn()
mockDelegate1.mockResolvedValue(30)
Expand Down
15 changes: 12 additions & 3 deletions packages/promise-cache/src/promiseCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,26 +120,28 @@ export class PromiseCache<U> {
* @param key Cache key.
* @param delegate The function to execute if the key is not in the cache.
* @param ttlInSeconds Time to live in seconds.
* @param ttlKey The key in the response object that contains the TTL.
* @returns The result of the delegate function.
*/
async wrap(
key: string,
delegate: () => Promise<U>,
ttlInSeconds?: number
ttlInSeconds?: number,
ttlKey?: string
): Promise<U> {
const now = Date.now()

// Normalize the key if case insensitive.
const effectiveKey = this.caseSensitive ? key : key.toLowerCase()

// Determine the TTL and unique cache key for this specific call.
const effectiveTTL =
let effectiveTTL =
ttlInSeconds !== undefined ? ttlInSeconds * 1000 : this.ttl

const cached = await this.persistor.get<U>(effectiveKey)

if (cached) {
if (cached.ttl !== effectiveTTL) {
if (!ttlKey && cached.ttl !== effectiveTTL) {
console.error(
`WARNING: TTL mismatch for key: ${effectiveKey}. It is recommended to use the same TTL for the same key.`
)
Expand All @@ -150,6 +152,13 @@ export class PromiseCache<U> {

// Execute the delegate, cache the response with the current timestamp, and return it.
const response = await delegate()

// Get the TTL from the response if a TTL key is provided.
if (ttlKey) {
const responseDict = response as Record<string, unknown>
effectiveTTL = Number(responseDict[ttlKey] as string) || effectiveTTL // Fall back to the default TTL if the TTL key is not found.
}

this.persistor.set(effectiveKey, {
value: response,
timestamp: now,
Expand Down

0 comments on commit 6d40aae

Please sign in to comment.