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

@@ -6,6 +6,7 @@ import { eq, and, or, desc, asc, exists, sql, ilike, inArray, lt, gte, isNull }
import { formatUserName } from "../utils/formatUserName";
import { FormsStorage } from "./forms.storage";
import { clampTasksLimit, type TasksCursor } from "../utils/task-pagination";
import { invalidateAccessibleTasksForUser } from "../utils/cache";
export class TasksCoreStorage extends FormsStorage {
// Tasks
async getTask(id: number, organizationId: number): Promise<Task | undefined> {
@@ -327,6 +328,11 @@ export class TasksCoreStorage extends FormsStorage {
position,
})
.returning();
// Создатель и ответственный получают доступ к новой задаче — сброс их кэша
if (task.createdBy) invalidateAccessibleTasksForUser(task.createdBy, task.organizationId);
if (task.assignedTo && task.assignedTo !== task.createdBy) {
invalidateAccessibleTasksForUser(task.assignedTo, task.organizationId);
}
return task;
}
@@ -369,6 +375,10 @@ export class TasksCoreStorage extends FormsStorage {
exists(db.select().from(forms).where(and(eq(forms.id, tasks.formId), eq(forms.organizationId, organizationId))))
))
.returning();
// Переназначение ответственного даёт доступ новому исполнителю
if ('assignedTo' in updates && updates.assignedTo != null) {
invalidateAccessibleTasksForUser(updates.assignedTo, organizationId);
}
return task;
}