/** * daemon/todoist-projects.ts — the project tree, cached. * * Needed because ingress can be granted to a subtree: a project that is not * itself listed may still be reachable through an ancestor that is. Answering * that requires knowing who a project's parent is, which is not in the webhook * payload. * * Cached with a short TTL rather than fetched per event. A webhook arrives in * bursts — a task added, a label applied, a comment posted, all within seconds — * and one project listing per event is exactly the kind of load that produced * the 401 bursts that cost two claims on 2026-08-01. * * Everything here keys on ID, never on name. Todoist's project search returns * nothing for names containing emoji, so "Archive 🎯" is invisible to a * name query — a resolver that fell back to matching names would silently find * no project and report it as absent rather than unsearchable. */ export interface ProjectNode { id: string; name: string; parentId?: string; } /** Drop the cache — used after a grant, and by tests. */ export declare function invalidateProjectTree(): void; export declare function projectTree(fetchImpl?: typeof fetch): Promise>; /** * The chain from a project up to the root, nearest ancestor first. * * Depth-capped: a cycle in the data would otherwise hang the receiver, and a * hung receiver is indistinguishable from a webhook that never arrived. */ export declare function ancestorsOf(projectId: string, tree: Map, maxDepth?: number): string[]; //# sourceMappingURL=todoist-projects.d.ts.map