/**
* In-memory cache for Todoist data
* Caches projects, labels, and sections (infrequently changing data)
*/
import type { Project, Label, Section } from '../types/index.js';
interface CacheEntry<T> {
data: T;
timestamp: number;
}
export class TodoistCache {
private projects: Map<string, CacheEntry<Project>> = new Map();
private labels: Map<string, CacheEntry<Label>> = new Map();
private sections: Map<string, CacheEntry<Section>> = new Map();
private allProjectsCache: CacheEntry<Project[]> | null = null;
private allLabelsCache: CacheEntry<Label[]> | null = null;
private allSectionsCache: CacheEntry<Section[]> | null = null;
private readonly ttlMs: number;
constructor(ttlMs: number = 5 * 60 * 1000) {
this.ttlMs = ttlMs;
}
// ─────────────────────────────────────────────────────────────
// Projects
// ─────────────────────────────────────────────────────────────
getProject(id: string): Project | null {
const entry = this.projects.get(id);
if (entry && !this.isStale(entry)) {
return entry.data;
}
return null;
}
setProject(project: Project): void {
this.projects.set(project.id, {
data: project,
timestamp: Date.now(),
});
}
getAllProjects(): Project[] | null {
if (this.allProjectsCache && !this.isStale(this.allProjectsCache)) {
return this.allProjectsCache.data;
}
return null;
}
setAllProjects(projects: Project[]): void {
this.allProjectsCache = {
data: projects,
timestamp: Date.now(),
};
// Also populate individual cache
for (const project of projects) {
this.setProject(project);
}
}
// ─────────────────────────────────────────────────────────────
// Labels
// ─────────────────────────────────────────────────────────────
getLabel(id: string): Label | null {
const entry = this.labels.get(id);
if (entry && !this.isStale(entry)) {
return entry.data;
}
return null;
}
setLabel(label: Label): void {
this.labels.set(label.id, {
data: label,
timestamp: Date.now(),
});
}
getAllLabels(): Label[] | null {
if (this.allLabelsCache && !this.isStale(this.allLabelsCache)) {
return this.allLabelsCache.data;
}
return null;
}
setAllLabels(labels: Label[]): void {
this.allLabelsCache = {
data: labels,
timestamp: Date.now(),
};
for (const label of labels) {
this.setLabel(label);
}
}
// ─────────────────────────────────────────────────────────────
// Sections
// ─────────────────────────────────────────────────────────────
getSection(id: string): Section | null {
const entry = this.sections.get(id);
if (entry && !this.isStale(entry)) {
return entry.data;
}
return null;
}
setSection(section: Section): void {
this.sections.set(section.id, {
data: section,
timestamp: Date.now(),
});
}
getAllSections(): Section[] | null {
if (this.allSectionsCache && !this.isStale(this.allSectionsCache)) {
return this.allSectionsCache.data;
}
return null;
}
setAllSections(sections: Section[]): void {
this.allSectionsCache = {
data: sections,
timestamp: Date.now(),
};
for (const section of sections) {
this.setSection(section);
}
}
// ─────────────────────────────────────────────────────────────
// Cache Management
// ─────────────────────────────────────────────────────────────
invalidateAll(): void {
this.projects.clear();
this.labels.clear();
this.sections.clear();
this.allProjectsCache = null;
this.allLabelsCache = null;
this.allSectionsCache = null;
}
invalidateProjects(): void {
this.projects.clear();
this.allProjectsCache = null;
}
invalidateLabels(): void {
this.labels.clear();
this.allLabelsCache = null;
}
invalidateSections(): void {
this.sections.clear();
this.allSectionsCache = null;
}
private isStale<T>(entry: CacheEntry<T>): boolean {
return Date.now() - entry.timestamp > this.ttlMs;
}
}