Skip to content

Commit eb590a4

Browse files
committed
cache-manager - adding back in ttl() function to get the expiration
1 parent 2a27b44 commit eb590a4

File tree

2 files changed

+108
-0
lines changed

2 files changed

+108
-0
lines changed

packages/cache-manager/src/index.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export type Cache = {
1717
// eslint-disable-next-line @typescript-eslint/ban-types
1818
get: <T>(key: string) => Promise<T | null>;
1919
mget: <T>(keys: string[]) => Promise<[T]>;
20+
ttl: (key: string) => Promise<number | undefined>;
2021
set: <T>(key: string, value: T, ttl?: number) => Promise<T>;
2122
mset: <T>(
2223
list: Array<{
@@ -105,6 +106,41 @@ export const createCache = (options?: CreateCacheOptions): Cache => {
105106
return result as [T];
106107
};
107108

109+
// eslint-disable-next-line @typescript-eslint/ban-types
110+
const ttl = async (key: string): Promise<number | null> => {
111+
let result = null;
112+
113+
if (nonBlocking) {
114+
try {
115+
result = await Promise.race(stores.map(async store => store.get(key, {raw: true})));
116+
if (result === undefined) {
117+
return null;
118+
}
119+
} catch (error) {
120+
eventEmitter.emit('ttl', {key, error});
121+
}
122+
} else {
123+
for (const store of stores) {
124+
try {
125+
const cacheValue = await store.get(key, {raw: true});
126+
if (cacheValue !== undefined) {
127+
result = cacheValue;
128+
eventEmitter.emit('ttl', {key, value: result});
129+
break;
130+
}
131+
} catch (error) {
132+
eventEmitter.emit('ttl', {key, error});
133+
}
134+
}
135+
}
136+
137+
if (result?.expires) {
138+
return result.expires;
139+
}
140+
141+
return null;
142+
};
143+
108144
const set = async <T>(stores: Keyv[], key: string, value: T, ttl?: number) => {
109145
try {
110146
if (nonBlocking) {
@@ -281,6 +317,7 @@ export const createCache = (options?: CreateCacheOptions): Cache => {
281317
return {
282318
get,
283319
mget,
320+
ttl,
284321
set: async <T>(key: string, value: T, ttl?: number) => set(stores, key, value, ttl),
285322
mset: async <T>(list: Array<{key: string; value: T; ttl?: number}>) => mset(stores, list),
286323
del,
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import {Keyv} from 'keyv';
2+
import {
3+
beforeEach, describe, expect, it,
4+
} from 'vitest';
5+
import {faker} from '@faker-js/faker';
6+
import {createCache} from '../src/index.js';
7+
import {sleep} from './sleep.js';
8+
9+
describe('get', () => {
10+
let keyv: Keyv;
11+
let cache: ReturnType<typeof createCache>;
12+
let ttl = 500;
13+
const data = {key: '', value: ''};
14+
15+
beforeEach(async () => {
16+
data.key = faker.string.alpha(20);
17+
data.value = faker.string.sample();
18+
ttl = faker.number.int({min: 500, max: 1000});
19+
keyv = new Keyv();
20+
cache = createCache({stores: [keyv]});
21+
});
22+
23+
it('basic', async () => {
24+
await cache.set(data.key, data.value);
25+
await expect(cache.ttl(data.key)).resolves.toEqual(null);
26+
});
27+
28+
it('expired', async () => {
29+
await cache.set(data.key, data.value, ttl);
30+
await sleep(ttl + 100);
31+
await expect(cache.ttl(data.key)).resolves.toEqual(null);
32+
});
33+
34+
it('error', async () => {
35+
await cache.set(data.key, data.value);
36+
keyv.get = () => {
37+
throw new Error('get error');
38+
};
39+
40+
await expect(cache.ttl(data.key)).resolves.toEqual(null);
41+
});
42+
it('error on non-blocking enabled', async () => {
43+
const secondKeyv = new Keyv();
44+
keyv.get = () => {
45+
throw new Error('get error');
46+
};
47+
48+
const cache = createCache({stores: [keyv, secondKeyv], nonBlocking: true});
49+
await cache.set(data.key, data.value);
50+
await expect(cache.ttl(data.key)).resolves.toEqual(null);
51+
});
52+
it('gets the expiration of a key', async () => {
53+
await cache.set(data.key, data.value, ttl);
54+
const expiration = Date.now() + ttl;
55+
await expect(cache.ttl(data.key)).resolves.toBeGreaterThanOrEqual(expiration - 100);
56+
});
57+
58+
it('gets the expiration of a key with nonBlocking', async () => {
59+
const secondKeyv = new Keyv();
60+
const cache = createCache({stores: [keyv, secondKeyv], nonBlocking: true});
61+
await cache.set(data.key, data.value, ttl);
62+
const expiration = Date.now() + ttl;
63+
await expect(cache.ttl(data.key)).resolves.toBeGreaterThanOrEqual(expiration - 100);
64+
});
65+
66+
it('gets null of a key with nonBlocking', async () => {
67+
const secondKeyv = new Keyv();
68+
const cache = createCache({stores: [keyv, secondKeyv], nonBlocking: true});
69+
await expect(cache.ttl('non-block-bad-key1')).resolves.toEqual(null);
70+
});
71+
});

0 commit comments

Comments
 (0)