diff --git a/packages/cache-manager/README.md b/packages/cache-manager/README.md index 69549a34..587f939b 100644 --- a/packages/cache-manager/README.md +++ b/packages/cache-manager/README.md @@ -165,7 +165,7 @@ const cache = createCache({ stores: [keyv] }); - **ttl**?: number - Default time to live in milliseconds. The time to live in milliseconds. This is the maximum amount of time that an item can be in the cache before it is removed. -- **refreshThreshold**?: number - Default refreshThreshold in milliseconds. +- **refreshThreshold**?: number | (value:T) => number - Default refreshThreshold in milliseconds. You can also provide a function that will return the refreshThreshold based on the value. If the remaining TTL is less than **refreshThreshold**, the system will update the value asynchronously in background. - **refreshAllStores**?: boolean - Default false @@ -319,7 +319,7 @@ See unit tests in [`test/clear.test.ts`](./test/clear.test.ts) for more informat Wraps a function in cache. The first time the function is run, its results are stored in cache so subsequent calls retrieve from cache instead of calling the function. -If `refreshThreshold` is set and the remaining TTL is less than `refreshThreshold`, the system will update the value asynchronously. In the meantime, the system will return the old value until expiration. +If `refreshThreshold` is set and the remaining TTL is less than `refreshThreshold`, the system will update the value asynchronously. In the meantime, the system will return the old value until expiration. You can also provide a function that will return the refreshThreshold based on the value `(value:T) => number`. ```typescript await cache.wrap('key', () => 1, 5000, 3000) @@ -341,6 +341,10 @@ await cache.wrap('key', () => 3, 5000, 3000) // return data from cache, function will not be called // => 2 +await cache.wrap('key', () => 4, 5000, 3000); +// return data from cache, function will not be called +// => 2 + await cache.wrap('error', () => { throw new Error('failed') }) diff --git a/packages/cache-manager/src/index.ts b/packages/cache-manager/src/index.ts index 8a136cc2..7cb1053a 100644 --- a/packages/cache-manager/src/index.ts +++ b/packages/cache-manager/src/index.ts @@ -41,7 +41,7 @@ export type Cache = { key: string, fnc: () => T | Promise, ttl?: number | ((value: T) => number), - refreshThreshold?: number + refreshThreshold?: number | ((value: T) => number) ) => Promise; on: ( event: E, @@ -252,7 +252,7 @@ export const createCache = (options?: CreateCacheOptions): Cache => { key: string, fnc: () => T | Promise, ttl?: number | ((value: T) => number), - refreshThreshold?: number, + refreshThreshold?: number | ((value: T) => number), ): Promise => coalesceAsync(`${_cacheId}::${key}`, async () => { let value: T | undefined; let i = 0; @@ -281,7 +281,7 @@ export const createCache = (options?: CreateCacheOptions): Cache => { return result; } - const shouldRefresh = lt(remainingTtl, refreshThreshold ?? options?.refreshThreshold); + const shouldRefresh = lt(remainingTtl, runIfFn(refreshThreshold, value) ?? options?.refreshThreshold); if (shouldRefresh) { coalesceAsync(`+++${_cacheId}__${key}`, fnc) diff --git a/packages/cache-manager/test/wrap.test.ts b/packages/cache-manager/test/wrap.test.ts index 733bd4c8..23456b61 100644 --- a/packages/cache-manager/test/wrap.test.ts +++ b/packages/cache-manager/test/wrap.test.ts @@ -85,6 +85,25 @@ describe('wrap', () => { expect(await cache.wrap(data.key, async () => 5, undefined, 500)).toEqual(4); }); + it('should allow refreshThreshold function on wrap function', async () => { + const config = {ttl: (v: number) => v * 1000, refreshThreshold: (v: number) => v * 500}; + + // 1st call should be cached + expect(await cache.wrap(data.key, async () => 1, config.ttl, config.refreshThreshold)).toEqual(1); + await sleep(501); + // Background refresh, but stale value returned + expect(await cache.wrap(data.key, async () => 2, config.ttl, config.refreshThreshold)).toEqual(1); + // New value in cache + expect(await cache.wrap(data.key, async () => 2, config.ttl, config.refreshThreshold)).toEqual(2); + await sleep(1001); + // No background refresh with the new override params + expect(await cache.wrap(data.key, async () => 3, undefined, 500)).toEqual(2); + await sleep(500); + // Background refresh, but stale value returned + expect(await cache.wrap(data.key, async () => 4, undefined, 500)).toEqual(2); + expect(await cache.wrap(data.key, async () => 5, undefined, 500)).toEqual(4); + }); + it('should support nested calls of other caches - no mutual state', async () => { const getValueA = vi.fn(() => 'A'); const getValueB = vi.fn(() => 'B');