Skip to content

Commit c0ec5a6

Browse files
authored
fix: null cache key (JhaSourav07#1761)
## Description Fixes JhaSourav07#1391 Adds runtime validation for `TTLCache` keys so `null` keys are rejected with a `TypeError`, and adds a focused boundary test to verify the cache remains unchanged after the rejected write. ## Pillar - [ ] 🎨 Pillar 1 — New Theme Design - [ ] 📐 Pillar 2 — Geometric SVG Improvement - [ ] 🕐 Pillar 3 — Timezone Logic Optimization - [x] 🛠️ Other (Bug fix, refactoring, docs) ## Visual Preview Not applicable — this PR only updates cache validation and unit tests. ## Checklist before requesting a review: - [x] I have read the `CONTRIBUTING.md` file. - [ ] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`). - [x] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise). - [ ] My commits follow the Conventional Commits format (e.g., `feat(themes): ...`, `fix(calculate): ...`). - [x] I have updated `README.md` if I added a new theme or URL parameter. - [ ] I have started the repo. - [ ] I have made sure that i have only one commit to merge in this PR. - [x] The SVG output matches the CommitPulse "premium quality" aesthetic standard (no raw elements, smooth animations, correct fonts). - [ ] (Recommended) I joined the CommitPulse Discord community for contributor discussions, mentorship, and faster PR support.
2 parents 696ddc0 + 92f437b commit c0ec5a6

2 files changed

Lines changed: 26 additions & 0 deletions

File tree

lib/cache.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,17 @@ describe('TTLCache', () => {
458458
cache.destroy();
459459
});
460460

461+
it('throws TypeError when setting a null key', () => {
462+
const cache = new TTLCache<string>();
463+
464+
expect(() => {
465+
cache.set(null as unknown as string, 'value', 60_000);
466+
}).toThrow(TypeError);
467+
expect(cache.size()).toBe(0);
468+
469+
cache.destroy();
470+
});
471+
461472
it('throws RangeError when ttlMs is 0 or negative', () => {
462473
const cache = new TTLCache<string>();
463474
expect(() => cache.set('key', 'value', 0)).toThrow(RangeError);

lib/cache.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ export class TTLCache<T> {
1919
private cleanupInterval: ReturnType<typeof setInterval> | null = null;
2020
private readonly maxSize?: number;
2121

22+
private static assertValidKey(key: unknown): asserts key is string {
23+
if (typeof key !== 'string') {
24+
throw new TypeError('Cache key must be a string');
25+
}
26+
}
27+
2228
/**
2329
* Creates a new TTL cache instance.
2430
*
@@ -62,6 +68,8 @@ export class TTLCache<T> {
6268
* const user = cache.get("user:1");
6369
*/
6470
get(key: string): T | null {
71+
TTLCache.assertValidKey(key);
72+
6573
const hit = this.store.get(key);
6674
if (!hit) return null;
6775

@@ -87,6 +95,8 @@ export class TTLCache<T> {
8795
* }
8896
*/
8997
has(key: string): boolean {
98+
TTLCache.assertValidKey(key);
99+
90100
const hit = this.store.get(key);
91101
if (!hit) return false;
92102

@@ -109,6 +119,8 @@ export class TTLCache<T> {
109119
* cache.delete("user:1");
110120
*/
111121
delete(key: string): boolean {
122+
TTLCache.assertValidKey(key);
123+
112124
return this.store.delete(key);
113125
}
114126

@@ -134,13 +146,16 @@ export class TTLCache<T> {
134146
* @returns `true` if the entry existed and was updated, `false` if missing or expired.
135147
*/
136148
update(key: string, value: T): boolean {
149+
TTLCache.assertValidKey(key);
150+
137151
const hit = this.store.get(key);
138152
if (!hit || Date.now() > hit.expiresAt) return false;
139153
hit.value = value;
140154
return true;
141155
}
142156

143157
set(key: string, value: T, ttlMs: number): void {
158+
TTLCache.assertValidKey(key);
144159
if (key === '') throw new Error('Cache key cannot be empty');
145160
if (ttlMs <= 0) throw new RangeError(`ttlMs must be positive, got ${ttlMs}`);
146161

0 commit comments

Comments
 (0)