Skip to content

Commit

Permalink
Verify cache expiration in has() and function() (#22)
Browse files Browse the repository at this point in the history
  • Loading branch information
fregante authored Sep 8, 2020
1 parent 807fd66 commit d61d250
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 16 deletions.
36 changes: 20 additions & 16 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ function timeInTheFuture(time: TimeDescriptor): number {
}

// @ts-ignore
const _get = getPromise((...args) => chrome.storage.local.get(...args));
const storageGet = getPromise((...args) => chrome.storage.local.get(...args));
// @ts-ignore
const _set = getPromise((...args) => chrome.storage.local.set(...args));
const storageSet = getPromise((...args) => chrome.storage.local.set(...args));
// @ts-ignore
const _remove = getPromise((...args) => chrome.storage.local.remove(...args));
const storageRemove = getPromise((...args) => chrome.storage.local.remove(...args));

type Primitive = boolean | number | string;
type Value = Primitive | Primitive[] | Record<string, any>;
Expand All @@ -38,13 +38,12 @@ interface CacheItem<TValue> {
type Cache<TValue extends Value = Value> = Record<string, CacheItem<TValue>>;

async function has(key: string): Promise<boolean> {
const internalKey = `cache:${key}`;
return internalKey in await _get<Cache>(internalKey);
return await _get(key, false) !== undefined;
}

async function get<TValue extends Value>(key: string): Promise<TValue | undefined> {
async function _get<TValue extends Value>(key: string, remove: boolean): Promise<CacheItem<TValue> | undefined> {
const internalKey = `cache:${key}`;
const storageData = await _get<Cache<TValue>>(internalKey);
const storageData = await storageGet<Cache<TValue>>(internalKey);
const cachedItem = storageData[internalKey];

if (cachedItem === undefined) {
Expand All @@ -53,11 +52,18 @@ async function get<TValue extends Value>(key: string): Promise<TValue | undefine
}

if (Date.now() > cachedItem.maxAge) {
await _remove(internalKey);
if (remove) {
await storageRemove(internalKey);
}

return;
}

return cachedItem.data;
return cachedItem;
}

async function get<TValue extends Value>(key: string): Promise<TValue | undefined> {
return (await _get<TValue>(key, true))?.data;
}

async function set<TValue extends Value>(key: string, value: TValue, maxAge: TimeDescriptor = {days: 30}): Promise<TValue> {
Expand All @@ -67,7 +73,7 @@ async function set<TValue extends Value>(key: string, value: TValue, maxAge: Tim
}

const internalKey = `cache:${key}`;
await _set({
await storageSet({
[internalKey]: {
data: value,
maxAge: timeInTheFuture(maxAge)
Expand All @@ -79,11 +85,11 @@ async function set<TValue extends Value>(key: string, value: TValue, maxAge: Tim

async function delete_(key: string): Promise<void> {
const internalKey = `cache:${key}`;
return _remove(internalKey);
return storageRemove(internalKey);
}

async function deleteWithLogic(logic?: (x: CacheItem<Value>) => boolean): Promise<void> {
const wholeCache = await _get<Record<string, any>>();
const wholeCache = await storageGet<Record<string, any>>();
const removableItems = [];
for (const [key, value] of Object.entries(wholeCache)) {
if (key.startsWith('cache:') && (logic?.(value) ?? true)) {
Expand All @@ -92,7 +98,7 @@ async function deleteWithLogic(logic?: (x: CacheItem<Value>) => boolean): Promis
}

if (removableItems.length > 0) {
await _remove(removableItems);
await storageRemove(removableItems);
}
}

Expand Down Expand Up @@ -133,9 +139,7 @@ function function_<

return (async (...args: TArgs) => {
const userKey = cacheKey ? cacheKey(args) : args[0] as string;
const internalKey = `cache:${userKey}`;
const storageData = await _get<Cache<TValue>>(internalKey);
const cachedItem = storageData[internalKey];
const cachedItem = await _get<TValue>(userKey, false);
if (cachedItem === undefined || shouldRevalidate?.(cachedItem.data)) {
return getSet(userKey, args);
}
Expand Down
34 changes: 34 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,24 @@ test.serial('get() with expired cache', async t => {
t.is(await cache.get('name'), undefined);
});

test.serial('has() with empty cache', async t => {
t.is(await cache.has('name'), false);
});

test.serial('has() with cache', async t => {
createCache(10, {
'cache:name': 'Rico'
});
t.is(await cache.has('name'), true);
});

test.serial('has() with expired cache', async t => {
createCache(-10, {
'cache:name': 'Rico'
});
t.is(await cache.has('name'), false);
});

test.serial('set() with undefined', async t => {
await cache.set('name');
// StorageArea.set should not be called with `undefined`
Expand Down Expand Up @@ -91,6 +109,22 @@ test.serial('function() with cache', async t => {
t.is(spy.callCount, 0);
});

test.serial('function() with expired cache', async t => {
createCache(-10, {
'cache:@anne': 'ONNA'
});

const spy = sinon.spy(getUsernameDemo);
const call = cache.function(spy);

t.is(await cache.get('@anne'), undefined);
t.is(await call('@anne'), 'ANNE');
t.is(chrome.storage.local.get.lastCall.args[0], 'cache:@anne');
t.true(spy.withArgs('@anne').calledOnce);
t.is(spy.callCount, 1);
t.is(chrome.storage.local.set.lastCall.args[0]['cache:@anne'].data, 'ANNE');
});

test.serial('function() with empty cache and staleWhileRevalidate', async t => {
const maxAge = 1;
const staleWhileRevalidate = 29;
Expand Down

0 comments on commit d61d250

Please sign in to comment.