Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cache-manager: wrap() method - support refreshThreshold argument as Function #975

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/cache-manager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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')
})
Expand Down
6 changes: 3 additions & 3 deletions packages/cache-manager/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export type Cache = {
key: string,
fnc: () => T | Promise<T>,
ttl?: number | ((value: T) => number),
refreshThreshold?: number
refreshThreshold?: number | ((value: T) => number)
) => Promise<T>;
on: <E extends keyof Events>(
event: E,
Expand Down Expand Up @@ -252,7 +252,7 @@ export const createCache = (options?: CreateCacheOptions): Cache => {
key: string,
fnc: () => T | Promise<T>,
ttl?: number | ((value: T) => number),
refreshThreshold?: number,
refreshThreshold?: number | ((value: T) => number),
): Promise<T> => coalesceAsync(`${_cacheId}::${key}`, async () => {
let value: T | undefined;
let i = 0;
Expand Down Expand Up @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions packages/cache-manager/test/wrap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
Loading