From 1e87549eb663f5e43d57edc2a85559db14695d09 Mon Sep 17 00:00:00 2001 From: arthurfiorette Date: Mon, 3 Jan 2022 10:00:39 -0300 Subject: [PATCH] feat: allow ttl to be defined based on the response --- README.md | 9 ++-- src/cache/cache.ts | 6 +-- src/interceptors/response.ts | 4 ++ test/interceptors/response.test.ts | 72 +++++++++++++++++++++++++++++- 4 files changed, 84 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5866f51..8777c02 100644 --- a/README.md +++ b/README.md @@ -531,10 +531,13 @@ You can override the request id used by this property. ### request.cache.ttl -The time that the request will remain in cache. Some custom storage implementations may -not respect 100% the time. +The time until the cached value is expired in milliseconds. -When using `interpretHeader`, this value is ignored. +If a function is used, it will receive the complete response and waits to return a TTL +value + +When using `interpretHeader: true`, this value will only be used if the interpreter can't +determine their TTL value to override this ### request.cache.interpretHeader diff --git a/src/cache/cache.ts b/src/cache/cache.ts index 76c5e37..1c5d6d1 100644 --- a/src/cache/cache.ts +++ b/src/cache/cache.ts @@ -11,14 +11,14 @@ export type CacheProperties = { /** * The time until the cached value is expired in milliseconds. * + * If a function is used, it will receive the complete response and waits to return a TTL value + * * When using `interpretHeader: true`, this value will only be used if the interpreter * can't determine their TTL value to override this * - * **Note**: a custom storage implementation may not respect this. - * * @default 1000 * 60 * 5 // 5 Minutes */ - ttl: number; + ttl: number | ((response: CacheAxiosResponse) => number | Promise); /** * If this interceptor should configure the cache from the request cache header When diff --git a/src/interceptors/response.ts b/src/interceptors/response.ts index f5193a6..5ae91ee 100644 --- a/src/interceptors/response.ts +++ b/src/interceptors/response.ts @@ -88,6 +88,10 @@ export class CacheResponseInterceptor const data = setupCacheData(response, cache.data); + if (typeof ttl === 'function') { + ttl = await ttl(response); + } + const newCache: CachedStorageValue = { state: 'cached', ttl, diff --git a/test/interceptors/response.test.ts b/test/interceptors/response.test.ts index 78255d4..978932b 100644 --- a/test/interceptors/response.test.ts +++ b/test/interceptors/response.test.ts @@ -1,5 +1,5 @@ import { Header } from '../../src/util/headers'; -import { mockAxios } from '../mocks/axios'; +import { mockAxios, XMockRandom } from '../mocks/axios'; describe('test request interceptor', () => { it('tests cache predicate integration', async () => { @@ -79,4 +79,74 @@ describe('test request interceptor', () => { expect(cache.state).toBe('cached'); expect(cache.ttl).toBe(defaultTtl); }); + + it('tests ttl with functions', async () => { + const axios = mockAxios(); + const id = 'my-id'; + + // first request (cached by tll) + + await axios.get('url', { + id, + cache: { + ttl: (resp) => { + expect(resp.cached).toBe(false); + expect(resp.config).toBeDefined(); + expect(resp.headers[XMockRandom]).not.toBeNaN(); + expect(resp.status).toBe(200); + expect(resp.statusText).toBe('200 OK'); + expect(resp.data).toBeTruthy(); + + return 100; + } + } + }); + + const cache1 = await axios.storage.get(id); + expect(cache1.state).toBe('cached'); + expect(cache1.ttl).toBe(100); + + // Second request (cached by ttl) + + const ttl = jest.fn().mockReturnValue(200); + + await axios.get('url', { + id, + cache: { ttl } + }); + + const cache2 = await axios.storage.get(id); + expect(cache2.state).toBe('cached'); + expect(cache2.ttl).toBe(100); + + expect(ttl).not.toHaveBeenCalled(); + + // Force invalidation + await axios.storage.remove(id); + }); + + it('tests async ttl function', async () => { + const axios = mockAxios(); + + // A lot of promises and callbacks + const { id } = await axios.get('url', { + cache: { + ttl: async () => { + await 0; + + return new Promise((res) => { + setTimeout(() => { + process.nextTick(() => { + res(173); + }); + }, 50); + }); + } + } + }); + + const cache = await axios.storage.get(id); + expect(cache.state).toBe('cached'); + expect(cache.ttl).toBe(173); + }); });