Skip to content

Commit fad8a36

Browse files
committed
fix(cache): rework cache clear to only evict with a regex pattern
1 parent 00c7278 commit fad8a36

File tree

4 files changed

+37
-12
lines changed

4 files changed

+37
-12
lines changed

src/services/common/base-client.test.ts

+10-2
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,20 @@ describe('base-client.ts', () => {
228228
expect(spyCacheStore.delete).toHaveBeenCalledWith('key');
229229
});
230230

231-
it('should delete a cached entry', async () => {
231+
it('should delete all cached entry matching regex', async () => {
232+
expect.assertions(1);
233+
234+
await client.clearCache('key', false);
235+
236+
expect(spyCacheStore.clear).toHaveBeenCalledWith('key');
237+
});
238+
239+
it('should delete all cached entry', async () => {
232240
expect.assertions(1);
233241

234242
await client.clearCache();
235243

236-
expect(spyCacheStore.clear).toHaveBeenCalledWith();
244+
expect(spyCacheStore.clear).toHaveBeenCalledWith(undefined);
237245
});
238246

239247
describe('cache', () => {

src/services/common/base-client.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -263,10 +263,11 @@ export abstract class BaseClient<
263263
* If no key is provided, clears the entire cache.
264264
*
265265
* @param key - The cache key.
266+
* @param exact - If the key to be evicted needs to be an exact match or a regex pattern (defaults to true).
266267
*/
267-
clearCache(key?: string) {
268-
if (key) return this._cache?.delete(key);
269-
return this._cache?.clear();
268+
clearCache(key?: string, exact = true) {
269+
if (key && exact) return this._cache?.delete(key);
270+
return this._cache?.clear(key);
270271
}
271272

272273
/**

src/utils/browser/browser-storage.utils.ts

+20-4
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ export const localStorage: chrome.storage.LocalStorageArea = window?.chrome?.sto
1515
*/
1616
export const sessionStorage: chrome.storage.StorageArea = window?.chrome?.storage?.session;
1717

18-
const filterObject = (object: Record<string, unknown>, regex: string) =>
19-
Object.fromEntries(Object.entries(object).filter(([key]) => new RegExp(regex).test(key)));
18+
const filterObject = (object: Record<string, unknown>, regex: string | RegExp) =>
19+
Object.fromEntries(Object.entries(object).filter(([key]) => (typeof regex === 'string' ? new RegExp(regex) : regex).test(key)));
20+
21+
const reverseFilterObject = (object: Record<string, unknown>, regex: string | RegExp) =>
22+
Object.fromEntries(Object.entries(object).filter(([key]) => !(typeof regex === 'string' ? new RegExp(regex) : regex).test(key)));
2023

2124
/**
2225
* This function is used to wrap the storage areas to provide type inference and a more convenient interface.
@@ -50,18 +53,31 @@ export const storageWrapper = (area: chrome.storage.StorageArea, name: string) =
5053

5154
window.trakt = { ...window.trakt, [name]: storage };
5255
return {
53-
getAll: async <T>(regex?: string): Promise<T> => (regex ? filterObject(storage.values, regex) : storage.values) as T,
56+
getAll: async <T>(regex?: string | RegExp): Promise<T> => (regex ? filterObject(storage.values, regex) : storage.values) as T,
5457
get: async <T>(key: string): Promise<T> => storage.values[key] as T,
5558
set: async <T>(key: string, value: T): Promise<void> => storage.setItem(key, value),
5659
remove: async (key: string): Promise<void> => storage.removeItem(key),
60+
removeAll: async (regex: string | RegExp): Promise<void> => {
61+
storage.values = reverseFilterObject(storage.values, regex);
62+
},
5763
clear: async (): Promise<void> => storage.clear(),
5864
};
5965
}
6066
return {
61-
getAll: <T>(regex?: string): Promise<T> => area.get().then(data => (regex ? filterObject(data, regex) : data) as T),
67+
getAll: <T>(regex?: string | RegExp): Promise<T> => area.get().then(data => (regex ? filterObject(data, regex) : data) as T),
6268
get: <T>(key: string): Promise<T> => area.get(key).then(({ [key]: value }) => value),
6369
set: <T>(key: string, value: T): Promise<void> => area.set({ [key]: value }),
6470
remove: (key: string): Promise<void> => area.remove(key),
71+
removeAll: async (regex: string | RegExp): Promise<void> => {
72+
const data = await area.get();
73+
const _regex = typeof regex === 'string' ? new RegExp(regex) : regex;
74+
await Promise.all(
75+
Object.keys(data).map(key => {
76+
if (_regex.test(key)) return area.remove(key);
77+
return Promise.resolve();
78+
}),
79+
);
80+
},
6581
clear: (): Promise<void> => area.clear(),
6682
};
6783
};

src/utils/cache.utils.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export type CacheStore<T = unknown> = {
1313
get(key: string): CacheStoreEntity<T> | Promise<CacheStoreEntity<T>> | undefined;
1414
set(key: string, value: CacheStoreEntity<T>): CacheStore<T> | Promise<CacheStore<T>>;
1515
delete(key: string): boolean | Promise<boolean>;
16-
clear(): void;
16+
clear(regex?: string): void;
1717
/** the duration in milliseconds after which the cache will be cleared */
1818
retention?: number;
1919
/** if true, the cache will be deleted if an error occurs */
@@ -90,7 +90,7 @@ export class ChromeCacheStore<T> implements CacheStore<T> {
9090
return true;
9191
}
9292

93-
async clear() {
94-
return this.store.clear();
93+
async clear(regex: string = '') {
94+
return this.store.removeAll(`^${this.prefix}:${regex}`);
9595
}
9696
}

0 commit comments

Comments
 (0)