perf: cache config in memory with write invalidation

This commit is contained in:
Brad Groux 2026-01-28 12:14:41 -06:00
parent db74b427ef
commit 7d3a9c404c
3 changed files with 67 additions and 0 deletions

View file

@ -2110,3 +2110,4 @@
{"type":"task.created","taskId":"task_20260128_Usuv2Y","project":"project-a","status":"todo","id":"evt_aG2Tll254MYY","timestamp":"2026-01-28T18:14:17.785Z"}
{"type":"task.created","taskId":"task_20260128_13CrZo","project":"project-b","status":"todo","id":"evt_YNGhKOJL_Tmu","timestamp":"2026-01-28T18:14:17.786Z"}
{"type":"task.created","taskId":"task_20260128_RJXi4k","status":"todo","id":"evt_21ZTMT4yYCOp","timestamp":"2026-01-28T18:14:17.799Z"}
{"type":"task.status_changed","taskId":"task_20260128_9t1KEa","project":"veritas-kanban","status":"in-progress","previousStatus":"todo","id":"evt_E2py7Rsv0FZs","timestamp":"2026-01-28T18:14:39.641Z"}

View file

@ -1,4 +1,15 @@
[
{
"id": "activity_1769624079642_g7c232tud",
"type": "status_changed",
"taskId": "task_20260128_9t1KEa",
"taskTitle": "SECURITY: Add server-side MIME type validation for attachments",
"details": {
"from": "todo",
"status": "in-progress"
},
"timestamp": "2026-01-28T18:14:39.642Z"
},
{
"id": "activity_1769623785282_5ct7g0hqu",
"type": "comment_added",

View file

@ -201,4 +201,59 @@ describe('ConfigService', () => {
expect(results[1]).toEqual(results[2]);
});
});
describe('Cache Behavior', () => {
it('should return cached config within TTL', async () => {
// First read populates cache
const config1 = await service.getConfig();
// Modify file on disk behind the service's back
const raw = JSON.parse(await fs.readFile(configFile, 'utf-8'));
raw.defaultAgent = 'amp';
// Mark as our own write so the file watcher ignores it,
// simulating a read within the TTL window
(service as any).lastWriteTime = Date.now();
await fs.writeFile(configFile, JSON.stringify(raw, null, 2));
// Should still return the cached version (TTL hasn't expired)
const config2 = await service.getConfig();
expect(config2.defaultAgent).toBe(config1.defaultAgent);
});
it('should re-read from disk after invalidateCache()', async () => {
const config1 = await service.getConfig();
expect(config1.defaultAgent).toBe('claude-code');
// Modify file on disk
const raw = JSON.parse(await fs.readFile(configFile, 'utf-8'));
raw.defaultAgent = 'amp';
// Suppress watcher for this manual write
(service as any).lastWriteTime = Date.now();
await fs.writeFile(configFile, JSON.stringify(raw, null, 2));
// Explicitly invalidate
service.invalidateCache();
const config2 = await service.getConfig();
expect(config2.defaultAgent).toBe('amp');
});
it('should update cache on saveConfig without re-reading disk', async () => {
const config = await service.getConfig();
config.defaultAgent = 'gemini';
await service.saveConfig(config);
// Should return updated value from cache, not disk
const config2 = await service.getConfig();
expect(config2.defaultAgent).toBe('gemini');
});
it('should clean up watcher on dispose()', async () => {
await service.getConfig(); // triggers watcher setup
service.dispose();
// After dispose, cache should be cleared
expect((service as any).config).toBeNull();
expect((service as any).watcher).toBeNull();
});
});
});