fix(chat): идемпотентность сообщений по clientMessageId — защита от дублей при повторной отправке
Первопричина дублей (3 одинаковых сообщения в задаче 1988): живой POST сорвался → сообщение попало в общую IndexedDB-очередь → background-sync разослал TRIGGER_SYNC всем вкладкам → каждая вкладка реплейнула одну и ту же запись (мьютекс _syncLock модульный, кросс-вкладочной координации нет). Гонка воспроизведена тестом tests/offline-queue-multitab-race.test.tsx (1 запись → 2 POST для двух вкладок). Вариант C — серверная идемпотентность: - task_messages.client_message_id + partial unique index (миграция 0086); - sendTaskMessage: повтор с тем же clientMessageId возвращает существующее сообщение БЕЗ insert и БЕЗ side-эффектов (уведомления/SSE/вебхуки не дублируются); гонка insert'ов ловится по 23505 → fallback на select; - клиент TaskChat шлёт crypto.randomUUID() в каждом сообщении; при офлайн-постановке тело с ключом сохраняется, все реплеи идут с одним ключом; - attachment-реплей: clientMessageId = id записи очереди (стабилен между реплеями); - юнит-тесты tests/task-message-idempotency.test.ts (3: быстрый путь, гонка 23505, проброс прочих ошибок).
This commit is contained in:
@@ -410,6 +410,19 @@ export class TasksStorage extends TasksCoreStorage {
|
||||
};
|
||||
}
|
||||
|
||||
/** Поиск id сообщения по клиентскому ключу идемпотентности (uuid от клиента) */
|
||||
async getTaskMessageIdByClientMessageId(taskId: number, clientMessageId: string): Promise<number | null> {
|
||||
const rows = await db
|
||||
.select({ id: taskMessages.id })
|
||||
.from(taskMessages)
|
||||
.where(and(
|
||||
eq(taskMessages.taskId, taskId),
|
||||
eq(taskMessages.clientMessageId, clientMessageId)
|
||||
))
|
||||
.limit(1);
|
||||
return rows[0]?.id ?? null;
|
||||
}
|
||||
|
||||
/** Батч-загрузка сообщений по списку id одним IN-запросом (сырые строки, tenant-фильтр) */
|
||||
async getTaskMessagesByIds(messageIds: number[], organizationId: number): Promise<TaskMessage[]> {
|
||||
if (messageIds.length === 0) return [];
|
||||
|
||||
Reference in New Issue
Block a user