diff --git a/src/cache.test.ts b/src/cache.test.ts index 1ca2b17..9b411a3 100644 --- a/src/cache.test.ts +++ b/src/cache.test.ts @@ -51,4 +51,32 @@ describe('cache', () => { const iceCreamStateAfter5Sec = get('ice cream state'); expect(iceCreamStateAfter5Sec).toEqual(undefined); // no longer defined, since the item level seconds until expiration was 5 }); + it('should accurately get keys', () => { + // create the cache + const { set, keys } = createCache(); + + // check key is added when value is set + set('meaning-of-life', '42'); + const keys1 = keys(); + expect(keys1.length).toEqual(1); + expect(keys1[0]).toEqual('meaning-of-life'); + + // check that there are no duplicates when key value is updated + set('meaning-of-life', '42.0'); + const keys2 = keys(); + expect(keys2.length).toEqual(1); + expect(keys2[0]).toEqual('meaning-of-life'); + + // check that multiple keys can be set + set('purpose-of-life', 'propagation'); + const keys3 = keys(); + expect(keys3.length).toEqual(2); + expect(keys3[1]).toEqual('purpose-of-life'); + + // check that invalidation removes the key + set('meaning-of-life', undefined); + const keys4 = keys(); + expect(keys4.length).toEqual(1); + expect(keys4[0]).toEqual('purpose-of-life'); + }); }); diff --git a/src/cache.ts b/src/cache.ts index 1c39c2c..2f78ff5 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,6 +1,7 @@ export interface SimpleInMemoryCache { get: (key: string) => T | undefined; set: (key: string, value: T, options?: { secondsUntilExpiration?: number }) => void; + keys: () => string[]; } export interface SimpleInMemoryCacheState { @@ -16,7 +17,18 @@ export const createCache = ({ defaultSecondsUntilExpiration = 5 * 60 }: { def const cache: SimpleInMemoryCacheState = {}; // define how to set an item into the cache - const set = (key: string, value: T, { secondsUntilExpiration = defaultSecondsUntilExpiration }: { secondsUntilExpiration?: number } = {}) => { + const set = ( + key: string, + value: T | undefined, + { secondsUntilExpiration = defaultSecondsUntilExpiration }: { secondsUntilExpiration?: number } = {}, + ) => { + // handle cache invalidation + if (value === undefined) { + delete cache[key]; + return; + } + + // handle setting const expiresAtMse = getMseNow() + secondsUntilExpiration * 1000; cache[key] = { value, expiresAtMse }; }; @@ -29,6 +41,12 @@ export const createCache = ({ defaultSecondsUntilExpiration = 5 * 60 }: { def return cacheContent.value; // otherwise, its in the cache and not expired, so return the value }; + // define how to grab all valid keys + const keys = () => + Object.entries(cache) + .filter(([_, value]) => value.expiresAtMse > getMseNow()) + .map(([key]) => key); + // return the api - return { set, get }; + return { set, get, keys }; };