"Simple" Cache
cache.ts
ts
interface CacheEntry<T> {
value: T;
expiresAt: number;
}
export default class SimpleCache<T> {
private cache = new Map<string, CacheEntry<T>>();
constructor(private ttl: number) {}
get(key: string): T | null {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
this.cache.delete(key);
return null;
}
return entry.value;
}
getAll(): Array<T> {
const now = Date.now();
const values: Array<T> = [];
for (const [key, entry] of this.cache.entries()) {
if (now > entry.expiresAt) {
this.cache.delete(key);
} else {
values.push(entry.value);
}
}
return values;
}
set(key: string, value: T) {
const expiresAt = Date.now() + this.ttl;
this.cache.set(key, {
value,
expiresAt
});
}
has(key: string): boolean {
const entry = this.cache.get(key);
if (!entry) return false;
if (Date.now() > entry.expiresAt) {
this.cache.delete(key);
return false;
}
return true;
}
expire(key: string) {
const entry = this.cache.get(key);
if (!entry) return;
this.cache.set(key, {
value: entry.value,
expiresAt: Date.now()
});
}
purge() {
const now = Date.now();
for (const [key, entry] of this.cache.entries()) {
if (now > entry.expiresAt) {
this.cache.delete(key);
}
}
}
clear() {
this.cache.clear();
}
keys(): string[] {
return [...this.cache.keys()];
}
size(): number {
return this.cache.size;
}
}cache.test.ts
ts
interface Post {
title: string;
subTitle?: string;
releaseDate: string;
categories: string[];
}
describe("SimpleCache", () => {
let simpleCache: SimpleCache<Post>;
beforeAll(() => {
simpleCache = new SimpleCache<Post>(300_000); // 5 Minutes
});
afterAll(() => {
simpleCache.clear();
});
it("should has one post", () => {
const wtfIsReactivityPost: Post = {
title: "WTF Is Reactivity !?",
subTitle: "Reactivity Models Explained",
releaseDate: "2024-12-18",
categories: ["programming", "javascript"]
};
simpleCache.set("wtf-is-reactivity", wtfIsReactivityPost);
expect(simpleCache.has("wtf-is-reactivity")).toBe(true);
});
it("should get one post", () => {
const tupleContextPatternPost: Post = {
title: "This Is Tuple Context Pattern",
releaseDate: "2025-04-05",
categories: ["react", "preact", "javascript", "typescript"]
};
simpleCache.set("tuple-context-pattern", tupleContextPatternPost);
expect(simpleCache.get("tuple-context-pattern")).toEqual(tupleContextPatternPost);
});
it("should get many posts", () => {
expect(simpleCache.getAll()).toHaveLength(simpleCache.size());
});
it("should expire and purge one post", async () => {
const nodeCleanArchPost: Post = {
title: "How To Build An API With Node, Without Additional Framework",
releaseDate: "2025-07-25",
categories: ["programming", "node", "sql", "javascript"]
};
simpleCache.set("node-clean-arch", nodeCleanArchPost);
expect(simpleCache.keys().includes("node-clean-arch")).toBe(true);
simpleCache.expire("node-clean-arch");
expect(simpleCache.keys().includes("node-clean-arch")).toBe(true);
await vi.waitFor(() => simpleCache.purge());
expect(simpleCache.keys().includes("node-clean-arch")).toBe(false);
});
});