refactor: make cache entry updates immutable

touchCacheEntry() now returns a new object via spread instead of mutating
the entry in-place. CacheManager.replaceCards() likewise creates a new entry
object instead of mutating existing fields. Update tests to assert the new
immutable semantics (returned value differs from input by reference).

https://claude.ai/code/session_016QvEfqw6YZ3RjwBHrJ4w8S
This commit is contained in:
Claude 2026-04-26 06:12:55 +00:00
parent 64ebbaef36
commit dbb8ea4a5b
No known key found for this signature in database
4 changed files with 19 additions and 14 deletions

View file

@ -131,14 +131,17 @@ export class CacheManager {
const entry = this.cache[filePath];
if (!entry) return false;
const now = new Date().toISOString();
entry.cards = (cards || []).map((card: ResolvedCard) => ({
title: card.title,
anchor: card.anchor,
gist: card.gist,
bullets: card.bullets || [],
}));
entry.updatedAt = now;
entry.lastAccessedAt = now;
this.cache[filePath] = {
...entry,
cards: (cards || []).map((card: ResolvedCard) => ({
title: card.title,
anchor: card.anchor,
gist: card.gist,
bullets: card.bullets || [],
})),
updatedAt: now,
lastAccessedAt: now,
};
await this.save();
return true;
}

View file

@ -4,8 +4,7 @@ import type { CacheEntry } from './types';
export function touchCacheEntry(entry: CacheEntry | null, now?: string): CacheEntry | null {
if (!entry) return null;
entry.lastAccessedAt = now || new Date().toISOString();
return entry;
return { ...entry, lastAccessedAt: now || new Date().toISOString() };
}
export function serializeCacheFile(entries: Record<string, CacheEntry>): string {

View file

@ -281,8 +281,10 @@ assert.deepStrictEqual(t.folderPathsForTarget('Reading'), ['Reading']);
assert.deepStrictEqual(t.folderPathsForTarget(''), []);
const untouchedCacheEntry = { generatedAt: '2024-01-01T00:00:00.000Z' };
assert.strictEqual(t.touchCacheEntry(null), null);
assert.strictEqual(t.touchCacheEntry(untouchedCacheEntry, '2024-01-05T00:00:00.000Z'), untouchedCacheEntry);
assert.strictEqual(untouchedCacheEntry.lastAccessedAt, '2024-01-05T00:00:00.000Z');
const touchedEntry = t.touchCacheEntry(untouchedCacheEntry, '2024-01-05T00:00:00.000Z');
assert.notStrictEqual(touchedEntry, untouchedCacheEntry, 'touchCacheEntry returns new object');
assert.strictEqual(touchedEntry.lastAccessedAt, '2024-01-05T00:00:00.000Z', 'touched entry has updated timestamp');
assert.strictEqual(untouchedCacheEntry.lastAccessedAt, undefined, 'original entry not mutated');
assert.strictEqual(t.shouldConfirmRegenerate({ updatedAt: '2024-01-05T00:00:00.000Z' }, true), true);
assert.strictEqual(t.shouldConfirmRegenerate({ updatedAt: '2024-01-05T00:00:00.000Z' }, false), false);
assert.strictEqual(t.shouldConfirmRegenerate({ generatedAt: '2024-01-01T00:00:00.000Z' }, true), false);

View file

@ -90,8 +90,9 @@ assert.deepStrictEqual(
assert.strictEqual(t.touchCacheEntry(null), null, 'touchCacheEntry on null returns null');
const entry = { generatedAt: '2024-01-01T00:00:00.000Z' };
t.touchCacheEntry(entry, '2024-06-01T00:00:00.000Z');
assert.strictEqual(entry.lastAccessedAt, '2024-06-01T00:00:00.000Z', 'touchCacheEntry sets lastAccessedAt');
const touched = t.touchCacheEntry(entry, '2024-06-01T00:00:00.000Z');
assert.strictEqual(touched.lastAccessedAt, '2024-06-01T00:00:00.000Z', 'touchCacheEntry sets lastAccessedAt on returned entry');
assert.strictEqual(entry.lastAccessedAt, undefined, 'touchCacheEntry does not mutate original entry');
const serialized = t.serializeCacheFile({ 'a.md': { cards: [] } });
const parsed = JSON.parse(serialized);