From c8413d6359b3e9f5ffdd04d513099f5a27e03d67 Mon Sep 17 00:00:00 2001 From: Yaron Yamin Date: Sun, 12 Jan 2025 17:09:30 +0200 Subject: [PATCH 1/3] cache-manager: wrap() method - support refreshThreshold argument as number or Function - (value:T) => number. similar to how ttl is supported --- packages/cache-manager/src/index.ts | 6 +++--- packages/cache-manager/test/wrap.test.ts | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/packages/cache-manager/src/index.ts b/packages/cache-manager/src/index.ts index 8a136cc2..4f5823b6 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..6bbaa7e2 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'); From b591f7ba33f83ca4189e2d003d16eb8b08cf0d76 Mon Sep 17 00:00:00 2001 From: Yaron Yamin Date: Mon, 13 Jan 2025 22:50:30 +0200 Subject: [PATCH 2/3] cache-manager: wrap() method - support refreshThreshold argument as a Function. fix lint issues --- packages/cache-manager/src/index.ts | 2 +- packages/cache-manager/test/wrap.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cache-manager/src/index.ts b/packages/cache-manager/src/index.ts index 4f5823b6..7cb1053a 100644 --- a/packages/cache-manager/src/index.ts +++ b/packages/cache-manager/src/index.ts @@ -281,7 +281,7 @@ export const createCache = (options?: CreateCacheOptions): Cache => { return result; } - const shouldRefresh = lt(remainingTtl, runIfFn(refreshThreshold, value) ?? 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 6bbaa7e2..23456b61 100644 --- a/packages/cache-manager/test/wrap.test.ts +++ b/packages/cache-manager/test/wrap.test.ts @@ -86,7 +86,7 @@ describe('wrap', () => { }); it('should allow refreshThreshold function on wrap function', async () => { - const config = {ttl: (v:number) => v * 1000, refreshThreshold: (v:number) => v * 500 }; + 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); From e78cb03c24f916f30c8db9e0df636c8decaf27f6 Mon Sep 17 00:00:00 2001 From: Jared Wray Date: Mon, 13 Jan 2025 16:53:33 -0800 Subject: [PATCH 3/3] Update README.md --- packages/cache-manager/README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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') })