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

Don't persist cache on reload #4854

Merged
merged 8 commits into from
Nov 8, 2024
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
33 changes: 6 additions & 27 deletions frontend/__tests__/utils/cache.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,14 @@ describe("Cache", () => {
const testTTL = 1000; // 1 second

beforeEach(() => {
localStorage.clear();
vi.useFakeTimers();
});

afterEach(() => {
vi.useRealTimers();
});

it("sets data in localStorage with expiration", () => {
cache.set(testKey, testData, testTTL);
const cachedEntry = JSON.parse(
localStorage.getItem(`app_cache_${testKey}`) || "",
);

expect(cachedEntry.data).toEqual(testData);
expect(cachedEntry.expiration).toBeGreaterThan(Date.now());
});

it("gets data from localStorage if not expired", () => {
it("gets data from memory if not expired", () => {
cache.set(testKey, testData, testTTL);

expect(cache.get(testKey)).toEqual(testData);
Expand All @@ -39,36 +28,26 @@ describe("Cache", () => {
vi.advanceTimersByTime(5 * 60 * 1000 + 1);

expect(cache.get(testKey)).toBeNull();
expect(localStorage.getItem(`app_cache_${testKey}`)).toBeNull();
});

it("returns null if cached data is expired", () => {
cache.set(testKey, testData, testTTL);

vi.advanceTimersByTime(testTTL + 1);
expect(cache.get(testKey)).toBeNull();
expect(localStorage.getItem(`app_cache_${testKey}`)).toBeNull();
});

it("deletes data from localStorage", () => {
it("deletes data from memory", () => {
cache.set(testKey, testData, testTTL);
cache.delete(testKey);

expect(localStorage.getItem(`app_cache_${testKey}`)).toBeNull();
expect(cache.get(testKey)).toBeNull();
});

it("clears all data with the app prefix from localStorage", () => {
it("clears all data with the app prefix from memory", () => {
cache.set(testKey, testData, testTTL);
cache.set("anotherKey", { data: "More data" }, testTTL);
cache.clearAll();

expect(localStorage.length).toBe(0);
});

it("does not retrieve non-prefixed data from localStorage when clearing", () => {
localStorage.setItem("nonPrefixedKey", "should remain");
cache.set(testKey, testData, testTTL);
cache.clearAll();
expect(localStorage.getItem("nonPrefixedKey")).toBe("should remain");
expect(cache.get(testKey)).toBeNull();
expect(cache.get("anotherKey")).toBeNull();
});
});
39 changes: 15 additions & 24 deletions frontend/src/utils/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,17 @@ type CacheEntry<T> = {
};

class Cache {
private prefix = "app_cache_";

private defaultTTL = 5 * 60 * 1000; // 5 minutes

/**
* Generate a unique key with prefix for local storage
* @param key The key to be stored in local storage
* @returns The unique key with prefix
*/
private getKey(key: CacheKey): string {
return `${this.prefix}${key}`;
}
private cacheMemory: Record<string, string> = {};

/**
* Retrieve the cached data from local storage
* @param key The key to be retrieved from local storage
* @returns The data stored in local storage
* Retrieve the cached data from memory
* @param key The key to be retrieved from memory
* @returns The data stored in memory
*/
public get<T>(key: CacheKey): T | null {
const cachedEntry = localStorage.getItem(this.getKey(key));
const cachedEntry = this.cacheMemory[key];
if (cachedEntry) {
const { data, expiration } = JSON.parse(cachedEntry) as CacheEntry<T>;
if (Date.now() < expiration) return data;
Expand All @@ -35,34 +26,34 @@ class Cache {
}

/**
* Store the data in local storage with expiration
* @param key The key to be stored in local storage
* @param data The data to be stored in local storage
* Store the data in memory with expiration
* @param key The key to be stored in memory
* @param data The data to be stored in memory
* @param ttl The time to live for the data in milliseconds
* @returns void
*/
public set<T>(key: CacheKey, data: T, ttl = this.defaultTTL): void {
const expiration = Date.now() + ttl;
const entry: CacheEntry<T> = { data, expiration };
localStorage.setItem(this.getKey(key), JSON.stringify(entry));
this.cacheMemory[key] = JSON.stringify(entry);
}

/**
* Remove the data from local storage
* @param key The key to be removed from local storage
* Remove the data from memory
* @param key The key to be removed from memory
* @returns void
*/
public delete(key: CacheKey): void {
localStorage.removeItem(this.getKey(key));
delete this.cacheMemory[key];
}

/**
* Clear all data with the app prefix from local storage
* Clear all data
* @returns void
*/
public clearAll(): void {
Object.keys(localStorage).forEach((key) => {
if (key.startsWith(this.prefix)) localStorage.removeItem(key);
Object.keys(this.cacheMemory).forEach((key) => {
delete this.cacheMemory[key];
});
}
}
Expand Down
Loading