diff --git a/docs/pages/per-request-configuration.md b/docs/pages/per-request-configuration.md index 92ae12a..f224d48 100644 --- a/docs/pages/per-request-configuration.md +++ b/docs/pages/per-request-configuration.md @@ -88,6 +88,10 @@ with the request id. Here's an example with some basic login: +Using a function instead of an object is supported but not recommended, as it's better to +just consume the response normally and write your own code after it. But it`s here in case +you need it. + ```ts // Some requests id's let profileInfoId; @@ -112,6 +116,7 @@ axios.post<{ auth: { user: User } }>( cachedValue.data = data; + // This returned value will be returned in next calls to the cache. return cachedValue; } } diff --git a/src/cache/cache.ts b/src/cache/cache.ts index 462800a..c7feb9b 100644 --- a/src/cache/cache.ts +++ b/src/cache/cache.ts @@ -60,9 +60,13 @@ export type CacheProperties = { * * The id used is the same as the id on `CacheRequestConfig['id']`, auto-generated or not. * + * **Using a function instead of an object is supported but not recommended, as it's + * better to just consume the response normally and write your own code after it. But + * it`s here in case you need it.** + * * @default {{}} */ - update: Record>; + update: CacheUpdater; /** * If the request should handle `ETag` and `If-None-Match` support. Use a string to diff --git a/src/util/types.ts b/src/util/types.ts index 1e602b2..4c16c19 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -35,13 +35,6 @@ export type KeyGenerator = ( export type MaybePromise = T | Promise | PromiseLike; -export type CacheUpdater = - | 'delete' - | (( - cached: Exclude, - response: CacheAxiosResponse - ) => MaybePromise); - /** * You can use a `number` to ensure an max time (in seconds) that the cache can be reused. * @@ -58,3 +51,29 @@ export type StaleIfErrorPredicate = cache: LoadingStorageValue & { previous: 'stale' }, error: Record ) => MaybePromise); + +export type CacheUpdaterFn = ( + response: CacheAxiosResponse +) => MaybePromise; + +/** + * A record for a custom cache updater for each specified request id. + * + * `delete` -> Deletes the request cache `predicate()` -> Determines if the cache can be + * reused, deleted or modified. + */ +export type CacheUpdaterRecord = { + [requestId: string]: + | 'delete' + | (( + cached: Exclude, + response: CacheAxiosResponse + ) => MaybePromise); +}; + +/** + * Updates any specified request cache by applying the response for this network call. + * + * You can use a function to implement your own cache updater function. + */ +export type CacheUpdater = CacheUpdaterFn | CacheUpdaterRecord; diff --git a/src/util/update-cache.ts b/src/util/update-cache.ts index 8d5f96d..ac62239 100644 --- a/src/util/update-cache.ts +++ b/src/util/update-cache.ts @@ -3,12 +3,17 @@ import type { AxiosStorage } from '../storage/types'; import type { CacheUpdater } from './types'; /** Function to update all caches, from CacheProperties.update, with the new data. */ -export async function updateCache( +export async function updateCache( storage: AxiosStorage, - data: CacheAxiosResponse, - entries: Record> + data: CacheAxiosResponse, + cacheUpdater: CacheUpdater ): Promise { - for (const [cacheKey, updater] of Object.entries(entries)) { + // Global cache update function. + if (typeof cacheUpdater === `function`) { + return cacheUpdater(data); + } + + for (const [cacheKey, updater] of Object.entries(cacheUpdater)) { if (updater === 'delete') { await storage.remove(cacheKey, data.config); continue; diff --git a/test/util/update-cache.test.ts b/test/util/update-cache.test.ts index d28ee9d..cb430cd 100644 --- a/test/util/update-cache.test.ts +++ b/test/util/update-cache.test.ts @@ -2,87 +2,105 @@ import type { CachedStorageValue } from '../../src/storage/types'; import { defaultKeyGenerator } from '../../src/util/key-generator'; import { mockAxios } from '../mocks/axios'; -const cacheKey = defaultKeyGenerator({ url: 'https://example.com/' }); -const cachedValue: CachedStorageValue = { +const CACHE_KEY = defaultKeyGenerator({ url: 'https://example.com/' }); +const CACHED_VALUE: CachedStorageValue = Object.freeze({ createdAt: Date.now(), state: 'cached', ttl: Number.MAX_SAFE_INTEGER, // never expires - data: { - data: 'value', - headers: {}, - status: 200, - statusText: '200 OK' - } -}; + data: { data: 'value', headers: {}, status: 200, statusText: '200 OK' } +}); describe('Tests update-cache', () => { - it('tests for delete key', async () => { + it('tests for delete key with CacheUpdaterFn', async () => { const axios = mockAxios({}); - await axios.storage.set(cacheKey, cachedValue); + await axios.storage.set(CACHE_KEY, CACHED_VALUE); await axios.get('other-key', { - cache: { update: { [cacheKey]: 'delete' } } + cache: { update: () => axios.storage.remove(CACHE_KEY) } }); - const cacheValue1 = await axios.storage.get(cacheKey); + const cacheValue1 = await axios.storage.get(CACHE_KEY); expect(cacheValue1).toStrictEqual({ state: 'empty' }); // - await axios.storage.set(cacheKey, cachedValue); + await axios.storage.set(CACHE_KEY, CACHED_VALUE); + + await axios.get('other-key2', { + cache: { update: () => axios.storage.remove(CACHE_KEY) } + }); + + const cacheValue3 = await axios.storage.get(CACHE_KEY); + expect(cacheValue3).toStrictEqual({ state: 'empty' }); + }); + + it('tests for delete key', async () => { + const axios = mockAxios({}); + await axios.storage.set(CACHE_KEY, CACHED_VALUE); + + await axios.get('other-key', { + cache: { update: { [CACHE_KEY]: 'delete' } } + }); + + const cacheValue1 = await axios.storage.get(CACHE_KEY); + expect(cacheValue1).toStrictEqual({ state: 'empty' }); + + // + + await axios.storage.set(CACHE_KEY, CACHED_VALUE); await axios.get('other-key2', { cache: { update: { - [cacheKey]: () => 'delete' + [CACHE_KEY]: () => 'delete' } } }); - const cacheValue2 = await axios.storage.get(cacheKey); + const cacheValue2 = await axios.storage.get(CACHE_KEY); expect(cacheValue2).toStrictEqual({ state: 'empty' }); // - await axios.storage.set(cacheKey, cachedValue); + await axios.storage.set(CACHE_KEY, CACHED_VALUE); await axios.get('other-key3', { - cache: { update: { [cacheKey]: () => Promise.resolve('delete') } } + cache: { update: { [CACHE_KEY]: () => Promise.resolve('delete') } } }); - const cacheValue3 = await axios.storage.get(cacheKey); + const cacheValue3 = await axios.storage.get(CACHE_KEY); expect(cacheValue3).toStrictEqual({ state: 'empty' }); }); it('tests for ignore key', async () => { const axios = mockAxios({}); - await axios.storage.set(cacheKey, cachedValue); + await axios.storage.set(CACHE_KEY, CACHED_VALUE); await axios.get('other-key', { - cache: { update: { [cacheKey]: () => 'ignore' } } + cache: { update: { [CACHE_KEY]: () => 'ignore' } } }); - const cacheValue = await axios.storage.get(cacheKey); - expect(cacheValue).toStrictEqual(cachedValue); + const cacheValue = await axios.storage.get(CACHE_KEY); + expect(cacheValue).toStrictEqual(CACHED_VALUE); // await axios.get('other-key2', { - cache: { update: { [cacheKey]: async () => Promise.resolve('ignore') } } + cache: { update: { [CACHE_KEY]: async () => Promise.resolve('ignore') } } }); - const cacheValue2 = await axios.storage.get(cacheKey); - expect(cacheValue2).toStrictEqual(cachedValue); + const cacheValue2 = await axios.storage.get(CACHE_KEY); + expect(cacheValue2).toStrictEqual(CACHED_VALUE); }); it('tests for new cached storage value', async () => { const axios = mockAxios({}); - await axios.storage.set(cacheKey, cachedValue); + await axios.storage.set(CACHE_KEY, CACHED_VALUE); await axios.get('other-key', { cache: { update: { - [cacheKey]: (cached) => { + [CACHE_KEY]: (cached) => { if (cached.state !== 'cached') { return 'ignore'; } @@ -96,28 +114,28 @@ describe('Tests update-cache', () => { } }); - const cacheValue = await axios.storage.get(cacheKey); - expect(cacheValue).not.toStrictEqual(cachedValue); + const cacheValue = await axios.storage.get(CACHE_KEY); + expect(cacheValue).not.toStrictEqual(CACHED_VALUE); expect(cacheValue.data?.data).toBe(1); }); it('tests updateCache with key is loading', async () => { const axios = mockAxios({}); - await axios.storage.set(cacheKey, { state: 'loading', previous: 'empty' }); + await axios.storage.set(CACHE_KEY, { state: 'loading', previous: 'empty' }); const handler = jest.fn(); await axios.get('other-key', { cache: { update: { - [cacheKey]: handler + [CACHE_KEY]: handler } } }); expect(handler).not.toHaveBeenCalled(); - const cacheValue = await axios.storage.get(cacheKey); + const cacheValue = await axios.storage.get(CACHE_KEY); expect(cacheValue.state).toBe('loading'); });