perf(access): TTL-кэш getAccessibleTaskIds с точечной инвалидацией

Шаг 0.12 плана production-готовности:
- accessibleTasksCache (45с) per userId:orgId внутри storage-метода
- инвалидация в storage-слое: роли, делегирования, form access,
  assignees, createTask/updateTask, appRole пользователя
- 9 новых юнит-тестов (65/65 зелёные)
This commit is contained in:
2026-09-07 22:27:33 +03:00
parent 82d577c797
commit 23c49adcd1
9 changed files with 231 additions and 2 deletions

View File

@@ -55,6 +55,15 @@ export class TtlCache {
this.invalidate(prefix);
}
/** Delete all keys that end with the given suffix */
invalidateSuffix(suffix: string): void {
for (const key of Array.from(this.store.keys())) {
if (key.endsWith(suffix)) {
this.store.delete(key);
}
}
}
/** Remove a single key */
delete(key: string): void {
this.store.delete(key);
@@ -73,3 +82,26 @@ export class TtlCache {
export const formsCache = new TtlCache(60);
export const tasksMinimalCache = new TtlCache(60);
/**
* Кэш множеств доступных задач (getAccessibleTaskIds).
* Ключ: accessible:<userId>:<organizationId>.
* Значение: Set<number> | null (null = admin/view_all, фильтрация не нужна).
* TTL 45 сек + явная инвалидация при изменении ролей, делегирований,
* правил доступа к формам и назначений задач.
*/
export const accessibleTasksCache = new TtlCache(45);
export function accessibleTasksKey(userId: number, organizationId: number): string {
return `accessible:${userId}:${organizationId}`;
}
/** Точечная инвалидация для одного пользователя (назначение, членство в роли и т.п.) */
export function invalidateAccessibleTasksForUser(userId: number, organizationId: number): void {
accessibleTasksCache.delete(accessibleTasksKey(userId, organizationId));
}
/** Массовая инвалидация по организации (роли, правила доступа к формам и т.п.) */
export function invalidateAccessibleTasksForOrg(organizationId: number): void {
accessibleTasksCache.invalidateSuffix(`:${organizationId}`);
}